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
package/src/compiler/manifest.ts
CHANGED
|
@@ -84,11 +84,18 @@ import {
|
|
|
84
84
|
validateRawFloats,
|
|
85
85
|
type RawFloatDiagnostic,
|
|
86
86
|
} from '../reference/raw-floats-validate';
|
|
87
|
+
import {
|
|
88
|
+
validateLabKatexImports,
|
|
89
|
+
type LabKatexDiagnostic,
|
|
90
|
+
} from '../reference/lab-katex-validate';
|
|
87
91
|
import {
|
|
88
92
|
gateContentMath,
|
|
89
93
|
pageScope,
|
|
94
|
+
scopeEntry,
|
|
90
95
|
type MathGateDiagnostic,
|
|
91
96
|
} from '../reference/gate-math';
|
|
97
|
+
import { gateIndices, type DerivedUse } from '../reference/gate-indices';
|
|
98
|
+
import { derivedNotationEntries } from '../reference/derived-entries';
|
|
92
99
|
import {
|
|
93
100
|
gateProseNotation,
|
|
94
101
|
gateProseNotationInAssessments,
|
|
@@ -102,12 +109,18 @@ import { gateCourseRules } from '../reference/gate-course-rules';
|
|
|
102
109
|
import { gateHeadingLength } from '../reference/gate-heading-length';
|
|
103
110
|
import { gateHeadingStructure } from '../reference/gate-heading-structure';
|
|
104
111
|
import { gatePartPages } from '../reference/gate-part-pages';
|
|
112
|
+
import { gateNonChapterPages } from '../reference/gate-non-chapter-pages';
|
|
113
|
+
import { findRetiredKeys, findUnsupportedKeys } from '../reference/gate-retired-keys';
|
|
114
|
+
import { findMarkupInProps } from '../reference/gate-prop-text';
|
|
115
|
+
import { gateMdxExpressions } from '../reference/gate-mdx-expression';
|
|
116
|
+
import { CITATION_BRACKET, bracketCitations, withoutLinkCitations } from '../reference/citation-format.mjs';
|
|
117
|
+
import { validateContentSchemas } from './content-schema';
|
|
105
118
|
import {
|
|
106
119
|
scanSectionHeadings,
|
|
107
120
|
scanTitledBlocks,
|
|
108
121
|
} from '../reference/sections.mjs';
|
|
109
122
|
import { knowledgeCheckAnchorId } from '../reference/knowledge-checks.mjs';
|
|
110
|
-
import { validateSectionRefs } from '../reference/sections-validate';
|
|
123
|
+
import { validateHeadingAnchors, validateSectionRefs } from '../reference/sections-validate';
|
|
111
124
|
import {
|
|
112
125
|
splitMixedContent,
|
|
113
126
|
type ContentSegment,
|
|
@@ -119,7 +132,6 @@ import type { GlyphScopeEntry } from '../reference/gloss';
|
|
|
119
132
|
import { resolveReferenceId } from '../reference/references';
|
|
120
133
|
import type { NotationGlossEntry } from '../reference/types';
|
|
121
134
|
import type {
|
|
122
|
-
EditorialStatus,
|
|
123
135
|
NotationBacklink,
|
|
124
136
|
NotationDefinitionRecord,
|
|
125
137
|
NotationDiagnostic,
|
|
@@ -129,11 +141,13 @@ import type {
|
|
|
129
141
|
import {
|
|
130
142
|
coursePartsOf,
|
|
131
143
|
normalizeBlockLabels,
|
|
144
|
+
normalizeCards,
|
|
132
145
|
normalizeLessonExports,
|
|
133
146
|
normalizeDisclosure,
|
|
134
147
|
normalizeNumbering,
|
|
135
148
|
type BlockLabels,
|
|
136
149
|
type CourseConfig,
|
|
150
|
+
type NormalizedCards,
|
|
137
151
|
type NormalizedLessonExports,
|
|
138
152
|
type NormalizedDisclosure,
|
|
139
153
|
type NormalizedNumbering,
|
|
@@ -158,9 +172,11 @@ import {
|
|
|
158
172
|
} from './knowledge-version';
|
|
159
173
|
import {
|
|
160
174
|
loadAbbreviationRegistryInput,
|
|
175
|
+
loadContentRecords,
|
|
161
176
|
loadCurriculumCatalog,
|
|
162
177
|
loadNotationDefinitions,
|
|
163
178
|
loadNotationRegistryInput,
|
|
179
|
+
loadNonChapterPages,
|
|
164
180
|
loadNotationSpecs,
|
|
165
181
|
loadPartOverviews,
|
|
166
182
|
loadSourceRecords,
|
|
@@ -290,8 +306,48 @@ import {
|
|
|
290
306
|
* state of a `<Fold>`; `disclosure.figures` now says whether a lab's tools
|
|
291
307
|
* start open; `diagnostics.folds` holds `fold-unnamed`
|
|
292
308
|
* (docs/plans/figures-and-short-captions.md, Parts 1–2).
|
|
309
|
+
*
|
|
310
|
+
* v25 (cards on non-chapter pages, rule S10): `pages` — every docs page that
|
|
311
|
+
* is not a chapter (the landing page, a part's overview page, …) with its
|
|
312
|
+
* `id`, `slug`, `file` and a `notation` bundle of the lesson shape, so the
|
|
313
|
+
* footer renders the cards its `[[key]]` terms and math open.
|
|
314
|
+
* `notation.bundles` and `notation.backlinks` now carry those pages' ids too.
|
|
315
|
+
*
|
|
316
|
+
* v26 (carried cards, additive): `cards` — the course's `cards` settings with
|
|
317
|
+
* the engine defaults applied: whether "Carry cards" starts on (rule K1).
|
|
318
|
+
*
|
|
319
|
+
* v27 (LaTeX export): `lessonExports.latex` and `lessonExports.latexPaper`;
|
|
320
|
+
* every `assessmentContent` item carries its `type`, a choice item's
|
|
321
|
+
* `options` (plain-text labels) and `correctOptionId`, and a
|
|
322
|
+
* numeric item's `answer`, so an answer key can be printed without re-reading
|
|
323
|
+
* `content/assessments/`.
|
|
324
|
+
*
|
|
325
|
+
* v28 (Markdown export): `lessonExports.markdown`.
|
|
326
|
+
*
|
|
327
|
+
* v29 (no editorial status): the draft / in-review / reviewed flag is gone —
|
|
328
|
+
* `lessons[].status`, `editorialStatus` on the notation definitions of
|
|
329
|
+
* `lessons[]` and `pages[]`, and `status` on `notation.definitions[]` and
|
|
330
|
+
* `notation.bundles[]`. `diagnostics.curriculum` carries `retired-key`, with
|
|
331
|
+
* `file` and `line`, for a content file that still sets the key, and the
|
|
332
|
+
* lesson-assumptions check reports `missing-assumptions`, not `review-state`.
|
|
333
|
+
*
|
|
334
|
+
* v30 (headings with math, additive): `lessons[].headings[].text` is what a
|
|
335
|
+
* reader sees — inline math as it reads (`$\explain{k}{R}$` is `R`, not its
|
|
336
|
+
* LaTeX) and a `[[key]]` term as its label — and `math: true` marks a heading
|
|
337
|
+
* with inline math. A heading's `slug` is unchanged. Each choice option of
|
|
338
|
+
* `assessmentContent` carries `labelSegments`, its label split into text and
|
|
339
|
+
* math like `promptSegments`, so a label's math renders.
|
|
340
|
+
*
|
|
341
|
+
* v31 (indexed symbols, rule N23): the `notation` bundle of `lessons[]` and
|
|
342
|
+
* `pages[]` gains `derived` — the derived cards its body math uses, each
|
|
343
|
+
* composed from a base entry and an index entry (`\mathbf{v}_0` from
|
|
344
|
+
* `\mathbf{v}` and `{}_0`), keyed `<base>.sub.<index>`. A notation
|
|
345
|
+
* definition carries `reads`, `range`, `fixedIndex` and an index entry's
|
|
346
|
+
* `formulaTemplate` when its entry sets them, and a bundle definition its
|
|
347
|
+
* `range`. `diagnostics.math` carries `index-explained`, `index-swallowed`,
|
|
348
|
+
* `index-range` and `derived-key-taken`.
|
|
293
349
|
*/
|
|
294
|
-
export const MANIFEST_SCHEMA_VERSION =
|
|
350
|
+
export const MANIFEST_SCHEMA_VERSION = 31;
|
|
295
351
|
|
|
296
352
|
const REPOSITORY_ROOT = process.cwd();
|
|
297
353
|
// Content is the top-level `content/` folder; the build output is `build/`
|
|
@@ -361,10 +417,11 @@ export interface ManifestLessonNotation {
|
|
|
361
417
|
readonly notation: string;
|
|
362
418
|
readonly label: string;
|
|
363
419
|
readonly summary: string;
|
|
364
|
-
readonly editorialStatus: EditorialStatus;
|
|
365
420
|
readonly seeAlso: readonly string[];
|
|
366
421
|
readonly units?: string;
|
|
367
422
|
readonly formula?: string;
|
|
423
|
+
/** Rule N23: the set a running index letter runs over, shown on its card (v31). */
|
|
424
|
+
readonly range?: string;
|
|
368
425
|
/** Symbols the formula names that have no card of their own (D15). */
|
|
369
426
|
readonly glosses: readonly NotationGlossEntry[];
|
|
370
427
|
/**
|
|
@@ -375,6 +432,27 @@ export interface ManifestLessonNotation {
|
|
|
375
432
|
readonly formulaScope: readonly GlyphScopeEntry[];
|
|
376
433
|
}
|
|
377
434
|
|
|
435
|
+
/**
|
|
436
|
+
* A derived card (rule N23, v31): the card of a symbol with a subscript that
|
|
437
|
+
* an index entry explains, composed from the `base` entry and the `index`
|
|
438
|
+
* entry by `derived-notation.mjs`. Its title and summary are about the
|
|
439
|
+
* indexed symbol; its formula names the base, whose card is one level deeper.
|
|
440
|
+
* It is listed nowhere — not in the Page notation panel, the glossary or an
|
|
441
|
+
* export's notation section — and opens only from the symbol.
|
|
442
|
+
*/
|
|
443
|
+
export interface ManifestDerivedNotation {
|
|
444
|
+
readonly key: string;
|
|
445
|
+
readonly base: string;
|
|
446
|
+
readonly index: string;
|
|
447
|
+
readonly notation: string;
|
|
448
|
+
readonly label: string;
|
|
449
|
+
readonly summary: string;
|
|
450
|
+
readonly units?: string;
|
|
451
|
+
readonly formula?: string;
|
|
452
|
+
readonly glosses: readonly NotationGlossEntry[];
|
|
453
|
+
readonly formulaScope: readonly GlyphScopeEntry[];
|
|
454
|
+
}
|
|
455
|
+
|
|
378
456
|
/**
|
|
379
457
|
* One keyed float of a lesson (v21): its key and number, and its caption pair
|
|
380
458
|
* — `short` (an equation's `\caption{…}`, or the `short` prop) and, for a
|
|
@@ -444,16 +522,18 @@ export interface ManifestLesson {
|
|
|
444
522
|
*/
|
|
445
523
|
readonly headings: readonly {
|
|
446
524
|
readonly slug: string;
|
|
525
|
+
/** What a reader sees: math as it reads, a `[[key]]` term as its label (v30). */
|
|
447
526
|
readonly text: string;
|
|
448
527
|
readonly level: number;
|
|
449
528
|
readonly number?: string;
|
|
529
|
+
/** The heading has inline math (v30). */
|
|
530
|
+
readonly math?: true;
|
|
450
531
|
}[];
|
|
451
532
|
readonly file: string;
|
|
452
533
|
readonly title: string;
|
|
453
534
|
readonly description: string;
|
|
454
535
|
/** Authored reader-facing estimate; `40` and `[30, 40]` both land here. */
|
|
455
536
|
readonly studyMinutes?: { readonly min: number; readonly max: number };
|
|
456
|
-
readonly status: EditorialStatus;
|
|
457
537
|
/** Ordered — the curriculum contract. */
|
|
458
538
|
readonly teaches: readonly string[];
|
|
459
539
|
readonly requires: readonly string[];
|
|
@@ -497,11 +577,27 @@ export interface ManifestLesson {
|
|
|
497
577
|
}[];
|
|
498
578
|
readonly definitionIds: readonly string[];
|
|
499
579
|
readonly definitions: readonly ManifestLessonNotation[];
|
|
580
|
+
/** The derived cards (rule N23) the page's body math uses (v31). */
|
|
581
|
+
readonly derived: readonly ManifestDerivedNotation[];
|
|
500
582
|
/** Every `[[key]]` / `\explain{key}` occurrence, resolved and located. */
|
|
501
583
|
readonly resolution: readonly ManifestNotationResolution[];
|
|
502
584
|
};
|
|
503
585
|
}
|
|
504
586
|
|
|
587
|
+
/**
|
|
588
|
+
* A docs page that is not a chapter (v25, rule S10) — the landing page, a
|
|
589
|
+
* part's overview page, any page outside a part directory. It numbers
|
|
590
|
+
* nothing, so it carries only what its cards need: the notation bundle its
|
|
591
|
+
* `[[key]]` terms and math reach, shared entries only.
|
|
592
|
+
*/
|
|
593
|
+
export interface ManifestPage {
|
|
594
|
+
/** `index`, `motion.index`, `glossary` — `lessonIdFromSlug` of the slug; never a lesson id. */
|
|
595
|
+
readonly id: string;
|
|
596
|
+
readonly slug: string;
|
|
597
|
+
readonly file: string;
|
|
598
|
+
readonly notation: ManifestLesson['notation'];
|
|
599
|
+
}
|
|
600
|
+
|
|
505
601
|
/** The reader-facing "about this material" bundle — the `[?]` panel's data. */
|
|
506
602
|
export interface ManifestAbout {
|
|
507
603
|
/** `explico`'s own identity, from its `package.json` (the version's golden source). */
|
|
@@ -536,6 +632,22 @@ export interface ManifestAssessmentItemContent {
|
|
|
536
632
|
readonly itemId: string;
|
|
537
633
|
readonly promptSegments: readonly ContentSegment[];
|
|
538
634
|
readonly explanationSegments: readonly ContentSegment[];
|
|
635
|
+
/** What kind of answer the item takes (v27). */
|
|
636
|
+
readonly type: 'single-choice' | 'numeric';
|
|
637
|
+
/**
|
|
638
|
+
* A choice item's options in authored order: the label as authored, and
|
|
639
|
+
* split into text and math like the prompt (`labelSegments`, v30), so a
|
|
640
|
+
* label's math renders.
|
|
641
|
+
*/
|
|
642
|
+
readonly options?: readonly {
|
|
643
|
+
readonly id: string;
|
|
644
|
+
readonly label: string;
|
|
645
|
+
readonly labelSegments: readonly ContentSegment[];
|
|
646
|
+
}[];
|
|
647
|
+
/** A choice item's correct option. */
|
|
648
|
+
readonly correctOptionId?: string;
|
|
649
|
+
/** A numeric item's accepted value and tolerance. */
|
|
650
|
+
readonly answer?: { readonly value: number; readonly tolerance: number };
|
|
539
651
|
}
|
|
540
652
|
|
|
541
653
|
/**
|
|
@@ -578,6 +690,8 @@ export interface Manifest {
|
|
|
578
690
|
readonly numbering: NormalizedNumbering;
|
|
579
691
|
/** How a check's and an example set's heading names its kind (`CourseConfig.blockLabels`). */
|
|
580
692
|
readonly blockLabels: BlockLabels;
|
|
693
|
+
/** How the explanation cards start for a reader, defaults applied (`CourseConfig.cards`). */
|
|
694
|
+
readonly cards: NormalizedCards;
|
|
581
695
|
/** Reader-facing identity + versions (the `[?]` About panel). */
|
|
582
696
|
readonly about: ManifestAbout;
|
|
583
697
|
readonly notation: {
|
|
@@ -597,6 +711,8 @@ export interface Manifest {
|
|
|
597
711
|
/** The course's parts in config order, with their chapters and overview pages. */
|
|
598
712
|
readonly parts: readonly ManifestPart[];
|
|
599
713
|
readonly lessons: readonly ManifestLesson[];
|
|
714
|
+
/** Every docs page that is not a chapter, by id (v25). */
|
|
715
|
+
readonly pages: readonly ManifestPage[];
|
|
600
716
|
/** Full competency prerequisite graph. */
|
|
601
717
|
readonly prereqEdges: readonly PrereqEdge[];
|
|
602
718
|
/** Equation identity (D7): every labelled display equation + the render-path lookup. */
|
|
@@ -660,8 +776,12 @@ export interface Manifest {
|
|
|
660
776
|
readonly equations: readonly EquationDiagnostic[];
|
|
661
777
|
/** `<Table>` identity checks (F-tables-figures) — same codes, `tab-` prefixed. */
|
|
662
778
|
readonly tables: readonly TableDiagnostic[];
|
|
663
|
-
/**
|
|
664
|
-
|
|
779
|
+
/**
|
|
780
|
+
* `<Figure>` identity checks (F-tables-figures) — same codes, `fig-`
|
|
781
|
+
* prefixed — and rule V3's `lab-katex-direct`: a file a lesson imports
|
|
782
|
+
* that calls KaTeX itself.
|
|
783
|
+
*/
|
|
784
|
+
readonly figures: readonly (FigureDiagnostic | LabKatexDiagnostic)[];
|
|
665
785
|
/** `<Diagram>` identity checks — same codes, `dia-` prefixed. */
|
|
666
786
|
readonly diagrams: readonly DiagramDiagnostic[];
|
|
667
787
|
/** Knowledge-check identity and `[[check-…]]` reference checks. */
|
|
@@ -691,7 +811,6 @@ export interface Manifest {
|
|
|
691
811
|
|
|
692
812
|
// --- compile ---------------------------------------------------------
|
|
693
813
|
|
|
694
|
-
const CITE_PATTERN = /\[@([a-z0-9][a-z0-9.-]*)(?:;[ \t\r\n]*([^\]]*))?\]/g;
|
|
695
814
|
|
|
696
815
|
/** Distinct `[@id; locator]` occurrences in a body, in first-appearance order. */
|
|
697
816
|
function lessonCitations(body: string): ManifestCitation[] {
|
|
@@ -700,13 +819,16 @@ function lessonCitations(body: string): ManifestCitation[] {
|
|
|
700
819
|
.replace(/`[^`\n]*`/g, '');
|
|
701
820
|
const seen = new Set<string>();
|
|
702
821
|
const citations: ManifestCitation[] = [];
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
const
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
822
|
+
// A citation in a link's text renders as typed (`citation-in-link`).
|
|
823
|
+
for (const match of withoutLinkCitations(withoutCode).matchAll(CITATION_BRACKET)) {
|
|
824
|
+
for (const citation of bracketCitations(match[1] ?? '', match[2])) {
|
|
825
|
+
const id = citation.id;
|
|
826
|
+
const locator = citation.locator || undefined;
|
|
827
|
+
const dedupeKey = `${id} ${locator ?? ''}`;
|
|
828
|
+
if (seen.has(dedupeKey)) continue;
|
|
829
|
+
seen.add(dedupeKey);
|
|
830
|
+
citations.push(locator === undefined ? { id } : { id, locator });
|
|
831
|
+
}
|
|
710
832
|
}
|
|
711
833
|
return citations;
|
|
712
834
|
}
|
|
@@ -770,7 +892,9 @@ function lessonNotationResolution(
|
|
|
770
892
|
: 'local'
|
|
771
893
|
: 'unresolved',
|
|
772
894
|
definitionId: definitionId ?? null,
|
|
773
|
-
|
|
895
|
+
// A page's own references are prose or math; a definition's formula
|
|
896
|
+
// reference is reported as the definition reference it always was.
|
|
897
|
+
kind: reference.kind === 'formula' ? 'definition' : reference.kind,
|
|
774
898
|
line: reference.source.line ?? null,
|
|
775
899
|
column: reference.source.column ?? null,
|
|
776
900
|
};
|
|
@@ -782,11 +906,14 @@ function lessonNotationBundle(
|
|
|
782
906
|
definitionsById: ReadonlyMap<string, NotationDefinitionRecord>,
|
|
783
907
|
resolution: readonly ManifestNotationResolution[],
|
|
784
908
|
cardsByKey: ReadonlyMap<string, GlyphScopeEntry>,
|
|
909
|
+
derivedUses: readonly DerivedUse[] = [],
|
|
785
910
|
): ManifestLesson['notation'] {
|
|
786
911
|
const definitions: ManifestLessonNotation[] = [];
|
|
912
|
+
const records: NotationDefinitionRecord[] = [];
|
|
787
913
|
for (const definitionId of bundle?.definitionIds ?? []) {
|
|
788
914
|
const definition = definitionsById.get(definitionId);
|
|
789
915
|
if (!definition) continue;
|
|
916
|
+
records.push(definition);
|
|
790
917
|
definitions.push({
|
|
791
918
|
id: definition.id,
|
|
792
919
|
key: definition.key,
|
|
@@ -794,7 +921,6 @@ function lessonNotationBundle(
|
|
|
794
921
|
notation: definition.notation,
|
|
795
922
|
label: definition.label,
|
|
796
923
|
summary: definition.summary,
|
|
797
|
-
editorialStatus: definition.status,
|
|
798
924
|
seeAlso: [...definition.seeAlso],
|
|
799
925
|
glosses: [...definition.glosses],
|
|
800
926
|
formulaScope: formulaGlyphScope({
|
|
@@ -810,12 +936,18 @@ function lessonNotationBundle(
|
|
|
810
936
|
...(definition.formula === undefined
|
|
811
937
|
? {}
|
|
812
938
|
: { formula: definition.formula }),
|
|
939
|
+
...(definition.range === undefined ? {} : { range: definition.range }),
|
|
813
940
|
});
|
|
814
941
|
}
|
|
815
942
|
return {
|
|
816
943
|
bindings: [...(bundle?.bindings ?? [])].map((binding) => ({ ...binding })),
|
|
817
944
|
definitionIds: [...(bundle?.definitionIds ?? [])],
|
|
818
945
|
definitions,
|
|
946
|
+
derived: derivedNotationEntries(
|
|
947
|
+
derivedUses,
|
|
948
|
+
new Map(records.map((record) => [record.key, record])),
|
|
949
|
+
cardsByKey,
|
|
950
|
+
),
|
|
819
951
|
resolution: [...resolution],
|
|
820
952
|
};
|
|
821
953
|
}
|
|
@@ -925,13 +1057,25 @@ export async function compileManifest(
|
|
|
925
1057
|
loadAbbreviationRegistryInput(),
|
|
926
1058
|
]);
|
|
927
1059
|
|
|
928
|
-
|
|
1060
|
+
// Rule S10: a page that is not a chapter explains its symbols like a
|
|
1061
|
+
// chapter does, so its references resolve through the same registry and its
|
|
1062
|
+
// text and math pass the same gates. It stays out of `notationInput.lessons`,
|
|
1063
|
+
// which is the chapter list every numbering and curriculum pass reads.
|
|
1064
|
+
const nonChapterPages = loadNonChapterPages();
|
|
1065
|
+
const withPages: typeof notationInput = {
|
|
1066
|
+
...notationInput,
|
|
1067
|
+
lessons: [
|
|
1068
|
+
...notationInput.lessons,
|
|
1069
|
+
...nonChapterPages.map((page) => page.notation),
|
|
1070
|
+
],
|
|
1071
|
+
};
|
|
1072
|
+
const registry = buildNotationRegistry(withPages);
|
|
929
1073
|
// Cards a `formula` may name with `\explain{key}` (D15). Shared entries
|
|
930
1074
|
// only: a page-local symbol is not addressable from another entry.
|
|
931
1075
|
const cardsByKey = new Map<string, GlyphScopeEntry>(
|
|
932
1076
|
notationInput.sharedDefinitions.map((definition) => [
|
|
933
1077
|
definition.key,
|
|
934
|
-
|
|
1078
|
+
scopeEntry(definition),
|
|
935
1079
|
]),
|
|
936
1080
|
);
|
|
937
1081
|
const definitionsById = new Map(
|
|
@@ -945,27 +1089,74 @@ export async function compileManifest(
|
|
|
945
1089
|
const assessmentsInput = catalog.assessments as unknown as {
|
|
946
1090
|
id: string;
|
|
947
1091
|
title?: string;
|
|
948
|
-
items?: {
|
|
1092
|
+
items?: {
|
|
1093
|
+
id?: string;
|
|
1094
|
+
type?: string;
|
|
1095
|
+
prompt?: string;
|
|
1096
|
+
explanation?: string;
|
|
1097
|
+
options?: { id?: string; label?: string }[];
|
|
1098
|
+
correctOptionId?: string;
|
|
1099
|
+
answer?: { value?: number; tolerance?: number };
|
|
1100
|
+
}[];
|
|
949
1101
|
}[];
|
|
950
1102
|
|
|
951
|
-
const
|
|
1103
|
+
const contentRecords = loadContentRecords();
|
|
1104
|
+
const curriculumIssues = [
|
|
1105
|
+
...validateCurriculum(catalog),
|
|
1106
|
+
...validateContentSchemas(contentRecords),
|
|
1107
|
+
...findRetiredKeys(contentRecords),
|
|
1108
|
+
...findUnsupportedKeys(contentRecords),
|
|
1109
|
+
...findMarkupInProps(contentRecords),
|
|
1110
|
+
];
|
|
952
1111
|
const alignmentIssues = validateNotationAlignment(registry, catalog);
|
|
953
1112
|
const mathEndAbbreviations = {
|
|
954
1113
|
allow: rules.strings('N12', 'allow'),
|
|
955
1114
|
deny: rules.strings('N12', 'deny'),
|
|
956
1115
|
};
|
|
1116
|
+
// A heading's text is what the reader sees: a `[[key]]` term in it reads as
|
|
1117
|
+
// its label, the page's own symbol first (rule S1 measures it, and the
|
|
1118
|
+
// outline records it).
|
|
1119
|
+
const sharedLabels = new Map(
|
|
1120
|
+
notationInput.sharedDefinitions.map((definition) => [definition.key, definition.label]),
|
|
1121
|
+
);
|
|
1122
|
+
const localLabelsByLesson = new Map(
|
|
1123
|
+
notationInput.lessons.map((lesson) => [
|
|
1124
|
+
lesson.lessonId,
|
|
1125
|
+
new Map(lesson.localDefinitions.map((definition) => [definition.key, definition.label])),
|
|
1126
|
+
]),
|
|
1127
|
+
);
|
|
1128
|
+
const termLabel = (lessonId: string, key: string): string | undefined =>
|
|
1129
|
+
localLabelsByLesson.get(lessonId)?.get(key) ?? sharedLabels.get(key);
|
|
1130
|
+
// Rule N23 and the derived cards a page uses: one resolver pass over every
|
|
1131
|
+
// body. The cards are built whatever the rule's switch says; its findings
|
|
1132
|
+
// drop with the rule (`dropDisabled`) and yield to a waiver.
|
|
1133
|
+
const indexGate = gateIndices(
|
|
1134
|
+
withPages.lessons,
|
|
1135
|
+
new Map(
|
|
1136
|
+
notationInput.sharedDefinitions.map((definition) => [definition.key, definition]),
|
|
1137
|
+
),
|
|
1138
|
+
{
|
|
1139
|
+
assessments: assessmentsInput,
|
|
1140
|
+
lessonAssessments: catalog.lessons.map((lesson) => ({
|
|
1141
|
+
lessonId: lesson.id,
|
|
1142
|
+
assessmentIds: lesson.assessments,
|
|
1143
|
+
})),
|
|
1144
|
+
},
|
|
1145
|
+
);
|
|
957
1146
|
const mathIssues = [
|
|
1147
|
+
...indexGate.diagnostics,
|
|
958
1148
|
...gateContentMath({
|
|
959
|
-
notationInput,
|
|
1149
|
+
notationInput: withPages,
|
|
960
1150
|
assessments: assessmentsInput,
|
|
961
1151
|
lessonAssessments: catalog.lessons.map((lesson) => ({
|
|
962
1152
|
lessonId: lesson.id,
|
|
963
1153
|
assessmentIds: lesson.assessments,
|
|
964
1154
|
})),
|
|
965
1155
|
}),
|
|
1156
|
+
...gateMdxExpressions(withPages.lessons),
|
|
966
1157
|
...(rules.isEnabled('N9')
|
|
967
1158
|
? [
|
|
968
|
-
...gateProseNotation(
|
|
1159
|
+
...gateProseNotation(withPages.lessons, {
|
|
969
1160
|
allowSpelledUnits: rules.strings('N9', 'allow'),
|
|
970
1161
|
}),
|
|
971
1162
|
...gateProseNotationInAssessments(assessmentsInput, {
|
|
@@ -975,13 +1166,13 @@ export async function compileManifest(
|
|
|
975
1166
|
: []),
|
|
976
1167
|
...(rules.isEnabled('N12')
|
|
977
1168
|
? [
|
|
978
|
-
...gateMathPunctuation(
|
|
1169
|
+
...gateMathPunctuation(withPages.lessons, mathEndAbbreviations),
|
|
979
1170
|
...gateMathPunctuationInAssessments(assessmentsInput, mathEndAbbreviations),
|
|
980
1171
|
]
|
|
981
1172
|
: []),
|
|
982
1173
|
...(rules.isEnabled('N14')
|
|
983
1174
|
? gateAbbreviations({
|
|
984
|
-
notationInput,
|
|
1175
|
+
notationInput: withPages,
|
|
985
1176
|
abbreviations: abbreviationsInput,
|
|
986
1177
|
courseAbbreviations: rules.settings('N14'),
|
|
987
1178
|
})
|
|
@@ -989,6 +1180,7 @@ export async function compileManifest(
|
|
|
989
1180
|
...(rules.isEnabled('S1')
|
|
990
1181
|
? gateHeadingLength(notationInput.lessons, {
|
|
991
1182
|
maxLength: rules.number('S1', 'maxLength'),
|
|
1183
|
+
labelFor: termLabel,
|
|
992
1184
|
})
|
|
993
1185
|
: []),
|
|
994
1186
|
...gateHeadingStructure(notationInput.lessons, {
|
|
@@ -998,7 +1190,9 @@ export async function compileManifest(
|
|
|
998
1190
|
// The course's own rules (`courseRules`): one pass for every enabled `X`
|
|
999
1191
|
// rule that carries a `forbid` pattern. A disabled one, and a guide rule
|
|
1000
1192
|
// with no pattern, do no work here.
|
|
1001
|
-
...gateCourseRules(
|
|
1193
|
+
...gateCourseRules(withPages.lessons, rules.course),
|
|
1194
|
+
// S10 is locked: a float on a non-chapter page has no number to render.
|
|
1195
|
+
...gateNonChapterPages(nonChapterPages),
|
|
1002
1196
|
];
|
|
1003
1197
|
|
|
1004
1198
|
// Pre-render every assessment `prompt` / `explanation` into text/math
|
|
@@ -1015,22 +1209,57 @@ export async function compileManifest(
|
|
|
1015
1209
|
assessmentsInput.map((assessment) => [assessment.id, assessment]),
|
|
1016
1210
|
);
|
|
1017
1211
|
const assessmentContent: ManifestAssessmentItemContent[] = [];
|
|
1212
|
+
// An assessment is page math: its indexed symbols take derived cards
|
|
1213
|
+
// (rule N23), which the lesson then ships with its body's.
|
|
1214
|
+
const assessmentDerived = new Map<string, Map<string, DerivedUse>>();
|
|
1018
1215
|
for (const lesson of catalog.lessons) {
|
|
1019
1216
|
if (lesson.assessments.length === 0) continue;
|
|
1020
1217
|
const lessonInput = notationLessonById.get(lesson.id);
|
|
1021
1218
|
if (!lessonInput) continue;
|
|
1022
1219
|
const scope = pageScope(lessonInput, cardsByKey);
|
|
1220
|
+
const derived = new Map<string, DerivedUse>();
|
|
1221
|
+
assessmentDerived.set(lesson.id, derived);
|
|
1222
|
+
const page = { derive: true, derived };
|
|
1023
1223
|
for (const assessmentId of lesson.assessments) {
|
|
1024
1224
|
const assessment = assessmentInputById.get(assessmentId);
|
|
1025
1225
|
if (!assessment?.items) continue;
|
|
1026
1226
|
for (const item of assessment.items) {
|
|
1027
1227
|
if (!item.id) continue;
|
|
1228
|
+
const numeric =
|
|
1229
|
+
item.type === 'numeric' &&
|
|
1230
|
+
typeof item.answer?.value === 'number' &&
|
|
1231
|
+
typeof item.answer.tolerance === 'number';
|
|
1028
1232
|
assessmentContent.push({
|
|
1029
1233
|
lessonId: lesson.id,
|
|
1030
1234
|
assessmentId,
|
|
1031
1235
|
itemId: item.id,
|
|
1032
|
-
promptSegments: splitMixedContent(item.prompt ?? '', scope),
|
|
1033
|
-
explanationSegments: splitMixedContent(item.explanation ?? '', scope),
|
|
1236
|
+
promptSegments: splitMixedContent(item.prompt ?? '', scope, page),
|
|
1237
|
+
explanationSegments: splitMixedContent(item.explanation ?? '', scope, page),
|
|
1238
|
+
type: item.type === 'numeric' ? 'numeric' : 'single-choice',
|
|
1239
|
+
...(item.options
|
|
1240
|
+
? {
|
|
1241
|
+
options: item.options.flatMap((option) =>
|
|
1242
|
+
option.id
|
|
1243
|
+
? [
|
|
1244
|
+
{
|
|
1245
|
+
id: option.id,
|
|
1246
|
+
label: option.label ?? '',
|
|
1247
|
+
labelSegments: splitMixedContent(option.label ?? '', scope, page),
|
|
1248
|
+
},
|
|
1249
|
+
]
|
|
1250
|
+
: [],
|
|
1251
|
+
),
|
|
1252
|
+
}
|
|
1253
|
+
: {}),
|
|
1254
|
+
...(item.correctOptionId ? { correctOptionId: item.correctOptionId } : {}),
|
|
1255
|
+
...(numeric
|
|
1256
|
+
? {
|
|
1257
|
+
answer: {
|
|
1258
|
+
value: item.answer!.value!,
|
|
1259
|
+
tolerance: item.answer!.tolerance!,
|
|
1260
|
+
},
|
|
1261
|
+
}
|
|
1262
|
+
: {}),
|
|
1034
1263
|
});
|
|
1035
1264
|
}
|
|
1036
1265
|
}
|
|
@@ -1060,9 +1289,44 @@ export async function compileManifest(
|
|
|
1060
1289
|
const lessonSlugs = [...notationInput.lessons]
|
|
1061
1290
|
.map((lesson) => lesson.lessonId.replace(/\./g, '/'))
|
|
1062
1291
|
.sort();
|
|
1063
|
-
|
|
1064
|
-
|
|
1292
|
+
// A part takes a sidebar group and an ordinal once it has a chapter or an
|
|
1293
|
+
// overview page; one with neither yet (planned, its directory not written)
|
|
1294
|
+
// is left out of both, with a warning, so every part number — a lesson's,
|
|
1295
|
+
// `parts[]`, the LaTeX export's — counts the same parts.
|
|
1296
|
+
const listedParts = coursePartsOf(courseConfig);
|
|
1297
|
+
const overviewPages = loadPartOverviews(listedParts);
|
|
1065
1298
|
const overviewByDir = new Map(overviewPages.map((page) => [page.dir, page]));
|
|
1299
|
+
const hasChapter = (dir: string) => lessonSlugs.some((slug) => slug.startsWith(`${dir}/`));
|
|
1300
|
+
const configuredParts = listedParts.filter(
|
|
1301
|
+
(part) => hasChapter(part.dir) || overviewByDir.has(part.dir),
|
|
1302
|
+
);
|
|
1303
|
+
for (const part of listedParts) {
|
|
1304
|
+
if (configuredParts.includes(part)) continue;
|
|
1305
|
+
curriculumIssues.push({
|
|
1306
|
+
kind: 'part-empty',
|
|
1307
|
+
severity: 'warning',
|
|
1308
|
+
message:
|
|
1309
|
+
`course.settings.json \`parts\` lists "${part.dir}" (${JSON.stringify(part.label)}), but ` +
|
|
1310
|
+
`content/docs/${part.dir}/ has no chapter and no overview page yet, so the part ` +
|
|
1311
|
+
'is left out of the sidebar and the numbering until it has one.',
|
|
1312
|
+
});
|
|
1313
|
+
}
|
|
1314
|
+
// A lesson whose directory no part lists has no part, no chapter number and
|
|
1315
|
+
// no place in the sidebar.
|
|
1316
|
+
const partDirs = new Set(listedParts.map((part) => part.dir));
|
|
1317
|
+
for (const lesson of notationInput.lessons) {
|
|
1318
|
+
const dir = lesson.lessonId.split('.')[0] ?? '';
|
|
1319
|
+
if (partDirs.has(dir)) continue;
|
|
1320
|
+
curriculumIssues.push({
|
|
1321
|
+
kind: 'lesson-outside-parts',
|
|
1322
|
+
message:
|
|
1323
|
+
`${lesson.source.file} is in content/docs/${dir}/, which no part in ` +
|
|
1324
|
+
`course.settings.json \`parts\` lists, so it has no part, no chapter number and no ` +
|
|
1325
|
+
`place in the sidebar. Add { "dir": "${dir}", "label": "…" } to \`parts\`, or move the ` +
|
|
1326
|
+
"lesson into a part's directory.",
|
|
1327
|
+
file: lesson.source.file,
|
|
1328
|
+
});
|
|
1329
|
+
}
|
|
1066
1330
|
// S2: every authored part page carries its bridge and scope paragraphs.
|
|
1067
1331
|
if (rules.isEnabled('S2')) {
|
|
1068
1332
|
mathIssues.push(
|
|
@@ -1077,10 +1341,12 @@ export async function compileManifest(
|
|
|
1077
1341
|
}
|
|
1078
1342
|
// Per-place waivers: drop the located findings each one covers, and
|
|
1079
1343
|
// report the waivers themselves — a stale one is a warning, a bad one an
|
|
1080
|
-
// error.
|
|
1344
|
+
// error. Chapters and non-chapter pages are scanned alike, since the text
|
|
1345
|
+
// and math gates run on both; S2 and S10 are per-file or locked, so no
|
|
1346
|
+
// waiver reaches them.
|
|
1081
1347
|
const waiverDiagnostics: WaiverDiagnostic[] = [];
|
|
1082
1348
|
const waiverRules = { courseRules: rules.course };
|
|
1083
|
-
const waivers =
|
|
1349
|
+
const waivers = withPages.lessons.flatMap((lesson) => {
|
|
1084
1350
|
const collected = collectWaivers(lesson, waiverRules);
|
|
1085
1351
|
waiverDiagnostics.push(...collected.diagnostics);
|
|
1086
1352
|
return collected.waivers;
|
|
@@ -1127,12 +1393,14 @@ export async function compileManifest(
|
|
|
1127
1393
|
>();
|
|
1128
1394
|
const chapterScoped = numberingSettings.floats === 'chapter';
|
|
1129
1395
|
let partCursor = 0;
|
|
1130
|
-
|
|
1396
|
+
// The part groups, in `configuredParts` order; the static Reference group
|
|
1397
|
+
// after them is no part. An overview-only part counts, as `parts[]` does.
|
|
1398
|
+
for (const group of sidebar.slice(0, configuredParts.length)) {
|
|
1399
|
+
partCursor += 1;
|
|
1131
1400
|
const lessonIds = group.items
|
|
1132
1401
|
.filter((item): item is string => typeof item === 'string')
|
|
1133
1402
|
.map(lessonIdFromSlug);
|
|
1134
|
-
if (lessonIds.length === 0) continue;
|
|
1135
|
-
partCursor += 1;
|
|
1403
|
+
if (lessonIds.length === 0) continue;
|
|
1136
1404
|
// Running totals for this part, advanced one chapter at a time; they
|
|
1137
1405
|
// only reach a number under the part scope.
|
|
1138
1406
|
const used = {
|
|
@@ -1184,6 +1452,7 @@ export async function compileManifest(
|
|
|
1184
1452
|
? { prefix: `${placement.part}.${placement.chapter}` }
|
|
1185
1453
|
: {}),
|
|
1186
1454
|
depth: numberingSettings.sections.depth,
|
|
1455
|
+
labelFor: (key: string) => termLabel(lesson.lessonId, key),
|
|
1187
1456
|
}) as ManifestLesson['headings'],
|
|
1188
1457
|
];
|
|
1189
1458
|
}),
|
|
@@ -1199,6 +1468,7 @@ export async function compileManifest(
|
|
|
1199
1468
|
(heading) => heading.slug,
|
|
1200
1469
|
),
|
|
1201
1470
|
})),
|
|
1471
|
+
nonChapterPages.map((page) => page.slug),
|
|
1202
1472
|
),
|
|
1203
1473
|
);
|
|
1204
1474
|
|
|
@@ -1337,6 +1607,52 @@ export async function compileManifest(
|
|
|
1337
1607
|
}),
|
|
1338
1608
|
]),
|
|
1339
1609
|
);
|
|
1610
|
+
// A heading's anchor must not be one the engine gives something else on
|
|
1611
|
+
// the page (`heading-anchor-collision`, keyed-floats invariant).
|
|
1612
|
+
const engineAnchorsFor = (lessonId: string, body: string): Map<string, string> => {
|
|
1613
|
+
const slug = lessonId.replace(/\./g, '/');
|
|
1614
|
+
const anchors = new Map<string, string>();
|
|
1615
|
+
const keyed = (
|
|
1616
|
+
floats: readonly { readonly key: string }[],
|
|
1617
|
+
prefix: string,
|
|
1618
|
+
owner: (key: string) => string,
|
|
1619
|
+
) => {
|
|
1620
|
+
for (const float of floats) anchors.set(`${prefix}-${float.key}`, owner(float.key));
|
|
1621
|
+
};
|
|
1622
|
+
keyed(equationsBySlug.get(slug) ?? [], 'eq', (key) => `the equation labelled eq:${key}`);
|
|
1623
|
+
keyed(tablesBySlug.get(slug) ?? [], 'tab', (key) => `<Table id="${key}">`);
|
|
1624
|
+
keyed(figuresBySlug.get(slug) ?? [], 'fig', (key) => `<Figure id="${key}">`);
|
|
1625
|
+
keyed(diagramsBySlug.get(slug) ?? [], 'dia', (key) => `<Diagram id="${key}">`);
|
|
1626
|
+
keyed(examplesBySlug.get(slug) ?? [], 'ex', (key) => `<CompactExamples id="${key}">`);
|
|
1627
|
+
for (const check of checksBySlug.get(slug) ?? []) {
|
|
1628
|
+
anchors.set(knowledgeCheckAnchorId(check.key), `the knowledge check ${check.key}`);
|
|
1629
|
+
}
|
|
1630
|
+
for (const [name, prefix] of [
|
|
1631
|
+
['Memo', 'memo'],
|
|
1632
|
+
['ShortNote', 'short-note'],
|
|
1633
|
+
] as const) {
|
|
1634
|
+
const tag = new RegExp(`<${name}\\b[^>]*?\\bid=["']([^"']+)["']`, 'g');
|
|
1635
|
+
for (const match of body.matchAll(tag)) {
|
|
1636
|
+
anchors.set(`${prefix}-${match[1]}`, `<${name} id="${match[1]}">`);
|
|
1637
|
+
}
|
|
1638
|
+
}
|
|
1639
|
+
if (deriveSources(body).length > 0) {
|
|
1640
|
+
anchors.set('references', 'the References section generated from the page’s citations');
|
|
1641
|
+
}
|
|
1642
|
+
return anchors;
|
|
1643
|
+
};
|
|
1644
|
+
mathIssues.push(
|
|
1645
|
+
...validateHeadingAnchors(
|
|
1646
|
+
notationInput.lessons.map((lesson) => ({
|
|
1647
|
+
file: lesson.source.file,
|
|
1648
|
+
body: lesson.body,
|
|
1649
|
+
...(lesson.bodyLine === undefined ? {} : { bodyLine: lesson.bodyLine }),
|
|
1650
|
+
headings: headingsByLessonId.get(lesson.lessonId) ?? [],
|
|
1651
|
+
anchors: engineAnchorsFor(lesson.lessonId, lesson.body),
|
|
1652
|
+
})),
|
|
1653
|
+
),
|
|
1654
|
+
);
|
|
1655
|
+
|
|
1340
1656
|
// The blocks that draw an `<h2>` where their tag stands, placed after the
|
|
1341
1657
|
// outline heading they follow — "On this page" lists them there (v23).
|
|
1342
1658
|
// `<AssessmentSet />` draws every check of the lesson, or the one its `id`
|
|
@@ -1369,6 +1685,16 @@ export async function compileManifest(
|
|
|
1369
1685
|
},
|
|
1370
1686
|
);
|
|
1371
1687
|
};
|
|
1688
|
+
// Rule V3: the course files a lesson's imports reach draw math through
|
|
1689
|
+
// `renderLabMath`, never KaTeX itself.
|
|
1690
|
+
const labKatexDiagnostics = validateLabKatexImports(
|
|
1691
|
+
notationInput.lessons.map((lesson) => ({
|
|
1692
|
+
lessonId: lesson.lessonId,
|
|
1693
|
+
file: lesson.source.file,
|
|
1694
|
+
body: lesson.body,
|
|
1695
|
+
})),
|
|
1696
|
+
REPOSITORY_ROOT,
|
|
1697
|
+
);
|
|
1372
1698
|
const rawFloatDiagnostics = validateRawFloats(
|
|
1373
1699
|
notationInput.lessons.map((lesson) => ({
|
|
1374
1700
|
lessonId: lesson.lessonId,
|
|
@@ -1412,7 +1738,6 @@ export async function compileManifest(
|
|
|
1412
1738
|
...(frontmatter.studyMinutes === undefined
|
|
1413
1739
|
? {}
|
|
1414
1740
|
: { studyMinutes: frontmatter.studyMinutes }),
|
|
1415
|
-
status: meta.status,
|
|
1416
1741
|
teaches: [...teaches],
|
|
1417
1742
|
requires: [...meta.requires],
|
|
1418
1743
|
assumptions: [...meta.assumptions],
|
|
@@ -1442,18 +1767,38 @@ export async function compileManifest(
|
|
|
1442
1767
|
availableDefinitionIds,
|
|
1443
1768
|
),
|
|
1444
1769
|
cardsByKey,
|
|
1770
|
+
[
|
|
1771
|
+
...(indexGate.derivedByLesson.get(id) ?? []),
|
|
1772
|
+
...(assessmentDerived.get(id)?.values() ?? []),
|
|
1773
|
+
],
|
|
1445
1774
|
),
|
|
1446
1775
|
});
|
|
1447
1776
|
}
|
|
1448
1777
|
lessons.sort((a, b) => a.id.localeCompare(b.id));
|
|
1778
|
+
const pages: ManifestPage[] = nonChapterPages
|
|
1779
|
+
.map((page) => ({
|
|
1780
|
+
id: page.id,
|
|
1781
|
+
slug: page.slug,
|
|
1782
|
+
file: page.file,
|
|
1783
|
+
notation: lessonNotationBundle(
|
|
1784
|
+
bundlesByLesson.get(page.id),
|
|
1785
|
+
definitionsById,
|
|
1786
|
+
lessonNotationResolution(
|
|
1787
|
+
page.notation,
|
|
1788
|
+
definitionsById,
|
|
1789
|
+
availableDefinitionIds,
|
|
1790
|
+
),
|
|
1791
|
+
cardsByKey,
|
|
1792
|
+
indexGate.derivedByLesson.get(page.id),
|
|
1793
|
+
),
|
|
1794
|
+
}))
|
|
1795
|
+
.sort((a, b) => a.id.localeCompare(b.id));
|
|
1449
1796
|
|
|
1450
1797
|
const consistencyIssues = runConsistencyChecks({
|
|
1451
1798
|
registry,
|
|
1452
|
-
notationInput,
|
|
1799
|
+
notationInput: withPages,
|
|
1453
1800
|
specs: loadNotationSpecs(),
|
|
1454
|
-
catalog,
|
|
1455
1801
|
lintIgnore: loadLintIgnore(),
|
|
1456
|
-
conventions: courseConfig.conventions,
|
|
1457
1802
|
operators: rules.settings('N8'),
|
|
1458
1803
|
romanLabels: rules.settings('N10'),
|
|
1459
1804
|
accents: rules.settings('N22'),
|
|
@@ -1528,6 +1873,7 @@ export async function compileManifest(
|
|
|
1528
1873
|
disclosure: normalizeDisclosure(courseConfig),
|
|
1529
1874
|
numbering: numberingSettings,
|
|
1530
1875
|
blockLabels,
|
|
1876
|
+
cards: normalizeCards(courseConfig),
|
|
1531
1877
|
about,
|
|
1532
1878
|
notation: {
|
|
1533
1879
|
definitions: registry.definitions,
|
|
@@ -1543,6 +1889,7 @@ export async function compileManifest(
|
|
|
1543
1889
|
sidebar,
|
|
1544
1890
|
parts: manifestParts,
|
|
1545
1891
|
lessons,
|
|
1892
|
+
pages,
|
|
1546
1893
|
prereqEdges,
|
|
1547
1894
|
equations: {
|
|
1548
1895
|
labels: equationValidation.labels,
|
|
@@ -1578,7 +1925,7 @@ export async function compileManifest(
|
|
|
1578
1925
|
consistency: dropDisabled(consistencyIssues, off),
|
|
1579
1926
|
equations: dropDisabled(equationValidation.diagnostics, off),
|
|
1580
1927
|
tables: dropDisabled(tableValidation.diagnostics, off),
|
|
1581
|
-
figures: dropDisabled(figureValidation.diagnostics, off),
|
|
1928
|
+
figures: dropDisabled([...figureValidation.diagnostics, ...labKatexDiagnostics], off),
|
|
1582
1929
|
diagrams: dropDisabled(diagramValidation.diagnostics, off),
|
|
1583
1930
|
checks: dropDisabled(checkValidation.diagnostics, off),
|
|
1584
1931
|
examples: dropDisabled(exampleValidation.diagnostics, off),
|