explico 0.13.1 → 0.15.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 +990 -1
- package/README.md +200 -30
- package/dist/compiler/astro.d.ts +15 -2
- package/dist/compiler/astro.js +40 -11
- package/dist/compiler/cli-main.d.ts +100 -0
- package/dist/compiler/cli-main.js +433 -0
- package/dist/compiler/cli-reference.d.ts +50 -0
- package/dist/compiler/cli-reference.js +213 -0
- package/dist/compiler/cli-verbs.d.ts +45 -0
- package/dist/compiler/cli-verbs.js +224 -0
- package/dist/compiler/cli-verify.d.ts +51 -0
- package/dist/compiler/cli-verify.js +151 -0
- package/dist/compiler/cli.d.ts +408 -24
- package/dist/compiler/cli.js +1328 -8
- package/dist/compiler/collections.d.ts +88 -1
- package/dist/compiler/collections.js +170 -25
- package/dist/compiler/content-release.d.ts +49 -0
- package/dist/compiler/content-release.js +63 -0
- package/dist/compiler/content-schemas.d.ts +2 -2
- package/dist/compiler/course-config.d.ts +1 -1
- package/dist/compiler/course-config.js +105 -1
- package/dist/compiler/decks.d.ts +161 -0
- package/dist/compiler/decks.js +235 -0
- package/dist/compiler/exports/combined-html.d.ts +5 -1
- package/dist/compiler/exports/deck-fit.d.ts +55 -0
- package/dist/compiler/exports/deck-fit.js +90 -0
- package/dist/compiler/exports/export-chapters.d.ts +37 -6
- package/dist/compiler/exports/export-chapters.js +64 -5
- package/dist/compiler/exports/export-pages.d.ts +30 -3
- package/dist/compiler/exports/export-pages.js +24 -1
- package/dist/compiler/exports/latex/beamerthemeexplico.sty +124 -0
- package/dist/compiler/exports/latex/cache.d.ts +78 -0
- package/dist/compiler/exports/latex/cache.js +129 -0
- package/dist/compiler/exports/latex/capture.d.ts +10 -1
- package/dist/compiler/exports/latex/capture.js +3 -1
- package/dist/compiler/exports/latex/compile.d.ts +13 -2
- package/dist/compiler/exports/latex/compile.js +12 -1
- package/dist/compiler/exports/latex/deck.d.ts +92 -0
- package/dist/compiler/exports/latex/deck.js +349 -0
- package/dist/compiler/exports/latex/document.d.ts +51 -6
- package/dist/compiler/exports/latex/document.js +71 -12
- package/dist/compiler/exports/latex/explico-common.sty +198 -0
- package/dist/compiler/exports/latex/explico.cls +13 -183
- package/dist/compiler/exports/latex/serialize.d.ts +60 -4
- package/dist/compiler/exports/latex/serialize.js +4 -2
- package/dist/compiler/exports/latex-export.d.ts +24 -2
- package/dist/compiler/exports/latex-export.js +107 -26
- package/dist/compiler/exports/markdown/serialize.d.ts +24 -4
- package/dist/compiler/exports/markdown/serialize.js +85 -8
- package/dist/compiler/exports/markdown-export.d.ts +6 -1
- package/dist/compiler/exports/markdown-export.js +50 -11
- package/dist/compiler/exports/standalone-html.d.ts +10 -5
- package/dist/compiler/exports/standalone-html.js +3 -3
- package/dist/compiler/exports/static-tree.d.ts +38 -1
- package/dist/compiler/exports/static-tree.js +120 -4
- package/dist/compiler/knowledge-version.d.ts +26 -2
- package/dist/compiler/knowledge-version.js +27 -17
- package/dist/compiler/lesson-derivation.d.ts +143 -6
- package/dist/compiler/lesson-derivation.js +145 -13
- package/dist/compiler/manifest.d.ts +133 -4
- package/dist/compiler/manifest.js +364 -58
- package/dist/compiler/page-versions.d.ts +184 -0
- package/dist/compiler/page-versions.js +217 -0
- package/dist/compiler/render-diagnostics.d.ts +5 -1
- package/dist/compiler/resolution-report.d.ts +24 -6
- package/dist/compiler/resolution-report.js +22 -4
- package/dist/compiler/validate-content.d.ts +7 -0
- package/dist/compiler/validate-content.js +160 -0
- package/dist/{course-config-rRtME5UV.d.ts → course-config-BLtIdkei.d.ts} +164 -1
- package/dist/curriculum/competency-graph.d.ts +54 -0
- package/dist/curriculum/competency-graph.js +41 -0
- package/dist/reference/base-library.d.mts +8 -0
- package/dist/reference/base-library.mjs +19 -0
- package/dist/reference/consistency.d.ts +8 -4
- package/dist/reference/consistency.js +82 -26
- package/dist/reference/deck-remark.d.mts +123 -0
- package/dist/reference/deck-remark.mjs +600 -0
- package/dist/reference/deck-source.d.ts +107 -0
- package/dist/reference/deck-source.js +140 -0
- package/dist/reference/decks-validate.d.ts +133 -0
- package/dist/reference/decks-validate.js +359 -0
- package/dist/reference/derived-entries.d.ts +5 -1
- package/dist/reference/diagram-fit.d.ts +6 -2
- package/dist/reference/diagram-fit.js +5 -1
- package/dist/reference/figure-symbols.d.ts +23 -6
- package/dist/reference/figure-symbols.js +51 -15
- package/dist/reference/float-name.d.ts +43 -0
- package/dist/reference/float-name.js +58 -0
- package/dist/reference/folds-validate.d.ts +1 -1
- package/dist/reference/gate-abbreviations.d.ts +1 -1
- package/dist/reference/gate-indices.js +5 -4
- package/dist/reference/gate-math.d.ts +29 -2
- package/dist/reference/gate-math.js +99 -18
- package/dist/reference/gate-part-pages.d.ts +1 -1
- package/dist/reference/lab-katex-validate.d.ts +10 -6
- package/dist/reference/lab-katex-validate.js +17 -9
- package/dist/reference/lab-math-scope.js +29 -4
- package/dist/reference/math-glyphs.d.mts +25 -0
- package/dist/reference/math-glyphs.mjs +189 -25
- package/dist/reference/page-notation.d.ts +22 -3
- package/dist/reference/page-notation.js +32 -10
- package/dist/reference/references.d.ts +14 -1
- package/dist/reference/references.js +13 -0
- package/dist/reference/registry-scope.d.mts +33 -0
- package/dist/reference/registry-scope.mjs +34 -0
- package/dist/reference/registry.js +29 -10
- package/dist/reference/rehype-deck.d.mts +6 -0
- package/dist/reference/rehype-deck.mjs +286 -0
- package/dist/reference/rehype-heading-anchors.d.mts +20 -0
- package/dist/reference/rehype-heading-anchors.mjs +48 -6
- package/dist/reference/remark-notation.mjs +249 -40
- package/dist/reference/source-lines.d.mts +12 -0
- package/dist/reference/source-lines.mjs +65 -0
- package/dist/reference/types.d.ts +28 -0
- package/dist/rules/catalog.d.ts +21 -4
- package/dist/rules/catalog.js +237 -2
- package/dist/rules/course-rules.d.ts +5 -1
- package/dist/rules/course-rules.js +17 -3
- package/dist/rules/course-rules.schema.json +12 -0
- package/dist/rules/in-effect.d.ts +13 -3
- package/dist/rules/in-effect.js +34 -10
- package/dist/rules/rules.schema.json +280 -1
- package/dist/rules/settings.d.ts +1 -1
- package/dist/session/PreferenceStore.d.ts +6 -0
- package/dist/session/PreferenceStore.js +18 -1
- package/dist/session/deck-memory.d.ts +116 -0
- package/dist/session/deck-memory.js +143 -0
- package/dist/session/reading-memory.d.ts +96 -0
- package/dist/session/reading-memory.js +133 -0
- package/docs/VERSIONING.md +21 -18
- package/docs/authoring/README.md +31 -9
- package/docs/authoring/agents.md +72 -18
- package/docs/authoring/cli-reference.json +2172 -0
- package/docs/authoring/cli.md +887 -0
- package/docs/authoring/components.md +15 -0
- package/docs/authoring/deck.md +612 -0
- package/docs/authoring/migrating.md +1195 -16
- package/docs/authoring/notation.md +49 -6
- package/docs/authoring/structure.md +80 -1
- package/docs/authoring/versioning.md +56 -3
- package/docs/authoring/visualizations.md +7 -2
- package/docs/course-agent/AGENTS.md +40 -0
- package/docs/course-agent/agents/rules-reviewer.md +33 -0
- package/docs/course-agent/skills/draft-lesson/SKILL.md +38 -0
- package/docs/course-agent/skills/migrate-course/SKILL.md +32 -0
- package/docs/course-agent/skills/release-content/SKILL.md +26 -0
- package/docs/diagram-scaling.md +148 -0
- package/docs/privacy.md +59 -0
- package/package.json +8 -2
- package/src/compiler/astro.ts +60 -21
- package/src/compiler/cli-main.ts +557 -0
- package/src/compiler/cli-reference.ts +285 -0
- package/src/compiler/cli-verbs.ts +270 -0
- package/src/compiler/cli-verify.ts +241 -0
- package/src/compiler/cli.ts +2119 -170
- package/src/compiler/collections.ts +284 -25
- package/src/compiler/content-release.ts +91 -0
- package/src/compiler/course-config.ts +267 -0
- package/src/compiler/decks.ts +455 -0
- package/src/compiler/exports/deck-fit.ts +127 -0
- package/src/compiler/exports/export-chapters.ts +105 -6
- package/src/compiler/exports/export-pages.ts +46 -2
- package/src/compiler/exports/latex/beamerthemeexplico.sty +124 -0
- package/src/compiler/exports/latex/cache.ts +183 -0
- package/src/compiler/exports/latex/capture.ts +5 -2
- package/src/compiler/exports/latex/compile.ts +18 -0
- package/src/compiler/exports/latex/deck.ts +419 -0
- package/src/compiler/exports/latex/document.ts +117 -21
- package/src/compiler/exports/latex/explico-common.sty +198 -0
- package/src/compiler/exports/latex/explico.cls +13 -183
- package/src/compiler/exports/latex/serialize.ts +25 -21
- package/src/compiler/exports/latex/texlive-packages.txt +6 -2
- package/src/compiler/exports/latex-export.ts +187 -45
- package/src/compiler/exports/markdown/serialize.ts +118 -9
- package/src/compiler/exports/markdown-export.ts +56 -11
- package/src/compiler/exports/standalone-html.ts +8 -7
- package/src/compiler/exports/static-tree.ts +175 -3
- package/src/compiler/knowledge-version.ts +56 -21
- package/src/compiler/lesson-derivation.ts +282 -16
- package/src/compiler/manifest.ts +568 -63
- package/src/compiler/page-versions.ts +401 -0
- package/src/compiler/resolution-report.ts +38 -7
- package/src/compiler/validate-content.ts +215 -0
- package/src/components/CourseContents.astro +124 -6
- package/src/components/CurriculumMap.astro +49 -1
- package/src/components/FloatIndex.astro +1 -1
- package/src/components/PartOverview.astro +51 -5
- package/src/components/assessments/AssessmentSet.astro +41 -15
- package/src/components/block-heading.ts +15 -6
- package/src/components/card-carry.ts +7 -4
- package/src/components/card-manager.ts +14 -2
- package/src/components/card-memory.ts +13 -9
- package/src/components/deck/DeckEnhancer.astro +13 -0
- package/src/components/deck/deck-context.ts +104 -0
- package/src/components/deck/deck-controller.ts +1287 -0
- package/src/components/deck/deck-map.ts +91 -0
- package/src/components/deck-lane.ts +163 -0
- package/src/components/diagram/Diagram.astro +131 -86
- package/src/components/disclosure/persisted-disclosure.ts +9 -1
- package/src/components/figure/Figure.astro +7 -2
- package/src/components/figure/FloatCaption.astro +5 -3
- package/src/components/fold/Fold.astro +2 -2
- package/src/components/notation/NotationFormula.astro +81 -11
- package/src/components/notation/NotationGlossary.astro +132 -1
- package/src/components/notation/NotationLayer.astro +15 -7
- package/src/components/starlight/AboutPanel.astro +186 -3
- package/src/components/starlight/CollapsibleSections.astro +24 -3
- package/src/components/starlight/DiagramEnhancer.astro +5 -1
- package/src/components/starlight/HeadingAnchorEnhancer.astro +1 -1
- package/src/components/starlight/LayoutHeader.astro +17 -0
- package/src/components/starlight/LayoutPageTitle.astro +110 -0
- package/src/components/starlight/LayoutSidebar.astro +807 -21
- package/src/components/starlight/LayoutSocialIcons.astro +62 -6
- package/src/components/starlight/LessonExportActions.astro +27 -9
- package/src/components/starlight/LessonFooter.astro +59 -0
- package/src/components/starlight/ReadingMemoryNotice.astro +114 -0
- package/src/components/starlight/TextSizeControl.astro +24 -13
- package/src/components/starlight/page-marks.ts +139 -0
- package/src/components/starlight/section-summary.ts +1 -1
- package/src/components/starlight/sidebar-editions.ts +245 -0
- package/src/components/table/Table.astro +15 -7
- package/src/content-config.ts +87 -1
- package/src/curriculum/competency-graph.ts +95 -0
- package/src/reference/base-library.mjs +19 -0
- package/src/reference/consistency.ts +137 -39
- package/src/reference/deck-remark.mjs +600 -0
- package/src/reference/deck-source.ts +273 -0
- package/src/reference/decks-validate.ts +604 -0
- package/src/reference/diagram-fit.ts +18 -4
- package/src/reference/diagram-layout.ts +3 -1
- package/src/reference/figure-symbols.ts +108 -31
- package/src/reference/folds-validate.ts +1 -1
- package/src/reference/gate-indices.ts +5 -3
- package/src/reference/gate-math.ts +169 -25
- package/src/reference/lab-katex-validate.ts +30 -15
- package/src/reference/lab-math-scope.ts +34 -3
- package/src/reference/math-glyphs.mjs +189 -25
- package/src/reference/page-notation.ts +69 -15
- package/src/reference/references.ts +28 -0
- package/src/reference/registry-scope.mjs +34 -0
- package/src/reference/registry.ts +41 -11
- package/src/reference/rehype-deck.mjs +286 -0
- package/src/reference/rehype-heading-anchors.mjs +48 -6
- package/src/reference/remark-notation.mjs +249 -40
- package/src/reference/source-lines.mjs +65 -0
- package/src/reference/types.ts +28 -0
- package/src/rules/catalog.ts +274 -4
- package/src/rules/course-rules.ts +21 -1
- package/src/rules/in-effect.ts +58 -11
- package/src/session/PreferenceStore.ts +23 -0
- package/src/session/deck-memory.ts +263 -0
- package/src/session/reading-memory.ts +243 -0
- package/src/styles/deck.css +1150 -0
- package/src/styles/global.css +958 -27
- package/src/styles/zones.css +277 -0
- /package/src/{components → reference}/float-name.ts +0 -0
package/src/compiler/manifest.ts
CHANGED
|
@@ -92,6 +92,8 @@ import {
|
|
|
92
92
|
gateContentMath,
|
|
93
93
|
pageScope,
|
|
94
94
|
scopeEntry,
|
|
95
|
+
scopeLocals,
|
|
96
|
+
suspendedOperators,
|
|
95
97
|
type MathGateDiagnostic,
|
|
96
98
|
} from '../reference/gate-math';
|
|
97
99
|
import { gateIndices, type DerivedUse } from '../reference/gate-indices';
|
|
@@ -129,7 +131,7 @@ import { loadLintIgnore } from '../reference/lint-ignore';
|
|
|
129
131
|
import { buildNotationRegistry } from '../reference/registry';
|
|
130
132
|
import { formulaGlyphScope } from '../reference/gloss';
|
|
131
133
|
import type { GlyphScopeEntry } from '../reference/gloss';
|
|
132
|
-
import {
|
|
134
|
+
import { pageScopeLessonIds, resolvePageReferenceId } from '../reference/references';
|
|
133
135
|
import type { NotationGlossEntry } from '../reference/types';
|
|
134
136
|
import type {
|
|
135
137
|
NotationBacklink,
|
|
@@ -142,12 +144,22 @@ import {
|
|
|
142
144
|
coursePartsOf,
|
|
143
145
|
normalizeBlockLabels,
|
|
144
146
|
normalizeCards,
|
|
147
|
+
normalizeSidebarMarks,
|
|
148
|
+
normalizeSidebarEditions,
|
|
149
|
+
normalizeReadingMemory,
|
|
150
|
+
normalizeAppearance,
|
|
145
151
|
normalizeLessonExports,
|
|
146
152
|
normalizeDisclosure,
|
|
147
153
|
normalizeNumbering,
|
|
154
|
+
normalizeDeck,
|
|
148
155
|
type BlockLabels,
|
|
149
156
|
type CourseConfig,
|
|
157
|
+
type NormalizedDeck,
|
|
150
158
|
type NormalizedCards,
|
|
159
|
+
type NormalizedSidebarMarks,
|
|
160
|
+
type NormalizedSidebarEditions,
|
|
161
|
+
type ReadingMemorySetting,
|
|
162
|
+
type NormalizedAppearance,
|
|
151
163
|
type NormalizedLessonExports,
|
|
152
164
|
type NormalizedDisclosure,
|
|
153
165
|
type NormalizedNumbering,
|
|
@@ -170,6 +182,15 @@ import {
|
|
|
170
182
|
resolveKnowledgeVersion,
|
|
171
183
|
type KnowledgeVersionDiagnostic,
|
|
172
184
|
} from './knowledge-version';
|
|
185
|
+
import {
|
|
186
|
+
deckStaleDiagnostics,
|
|
187
|
+
nextPageVersions,
|
|
188
|
+
PAGE_VERSIONS_FILE,
|
|
189
|
+
pageSourceDigest,
|
|
190
|
+
readPageVersions,
|
|
191
|
+
sinceOf,
|
|
192
|
+
type ManifestPageVersions,
|
|
193
|
+
} from './page-versions';
|
|
173
194
|
import {
|
|
174
195
|
loadAbbreviationRegistryInput,
|
|
175
196
|
loadContentRecords,
|
|
@@ -180,12 +201,23 @@ import {
|
|
|
180
201
|
loadNotationSpecs,
|
|
181
202
|
loadPartOverviews,
|
|
182
203
|
loadSourceRecords,
|
|
204
|
+
loadDeckFiles,
|
|
205
|
+
loadDeckNotationPages,
|
|
206
|
+
loadLessonFiles,
|
|
207
|
+
type LessonDeckFile,
|
|
208
|
+
type PartDeckFile,
|
|
183
209
|
} from './collections';
|
|
210
|
+
import { buildDecks, chapterEquationLatex, type BuiltDeck, type DeckPartFacts } from './decks';
|
|
211
|
+
import { readDeckSource } from '../reference/deck-source';
|
|
212
|
+
import { coveredSections, type ChapterFloats } from '../reference/decks-validate';
|
|
184
213
|
import {
|
|
185
214
|
buildSidebar,
|
|
186
215
|
deriveSources,
|
|
187
216
|
isLessonSlug,
|
|
188
217
|
lessonIdFromSlug,
|
|
218
|
+
mergeTrackOrder,
|
|
219
|
+
sidebarLessonSlug,
|
|
220
|
+
withPartDeckFrames,
|
|
189
221
|
type SidebarGroup,
|
|
190
222
|
} from './lesson-derivation';
|
|
191
223
|
|
|
@@ -346,8 +378,65 @@ import {
|
|
|
346
378
|
* `formulaTemplate` when its entry sets them, and a bundle definition its
|
|
347
379
|
* `range`. `diagnostics.math` carries `index-explained`, `index-swallowed`,
|
|
348
380
|
* `index-range` and `derived-key-taken`.
|
|
381
|
+
*
|
|
382
|
+
* v32 (a page's own version, rule C5): `lessons[].since` and
|
|
383
|
+
* `parts[].overview.since` carry the knowledge version each chapter's and
|
|
384
|
+
* part page's content last changed in (`null` for a change not yet
|
|
385
|
+
* released), read from the committed `content/versions.json` through
|
|
386
|
+
* `pageVersions` — the record as `validate:content` writes it.
|
|
387
|
+
* `about.knowledge` gains `releases` (every released entry with its date)
|
|
388
|
+
* and `unreleased` (whether `## [Unreleased]` holds bullets).
|
|
389
|
+
* `diagnostics.knowledge` can carry `page-versions-invalid`.
|
|
390
|
+
* `sidebarMarks` carries the course's `sidebarMarks` setting, defaults
|
|
391
|
+
* applied: whether the sidebar marks a page the reader has not opened yet,
|
|
392
|
+
* and one whose content changed since they last did. `readingMemory` says
|
|
393
|
+
* whether the course asks the reader to keep that memory (`ask`) or never
|
|
394
|
+
* keeps it (`off`).
|
|
395
|
+
* v33 (decks, additive): `decks` lists each lesson's slide edition
|
|
396
|
+
* (`<lesson>.deck.mdx`) with its frames, numbered as the deck shows them;
|
|
397
|
+
* `deck` carries the course's `deck` settings, defaults applied, and the
|
|
398
|
+
* canvas; `lessons[].editions` says which editions a lesson has
|
|
399
|
+
* (`chapter`, `deck`). A slides-first lesson (a deck with no chapter) is a
|
|
400
|
+
* lesson like any other, with `editions: ["deck"]`. `decks[].since` is the
|
|
401
|
+
* version a deck's page last changed in (rule C5), as a chapter's `since`.
|
|
402
|
+
* In `sidebar`, a deck beside its chapter follows the chapter's slug as
|
|
403
|
+
* `{ slug, label: "Slides", attrs: { class: "explico-deck-row" } }`, and a
|
|
404
|
+
* slides-first lesson is `{ slug, attrs: { class: "explico-deck-lesson" } }`
|
|
405
|
+
* in place of its slug (`sidebarLessonSlug` reads either kind of lesson).
|
|
406
|
+
*
|
|
407
|
+
* v34 (editions in the sidebar): `sidebarEditions` carries the course's
|
|
408
|
+
* `sidebarEditions` setting, defaults applied: the edition a lesson's row
|
|
409
|
+
* opens (`lead`: `chapter` or `slides`) and whether the row lists the other
|
|
410
|
+
* (`other`: `listed` or `hidden`). In `sidebar`, every item of a lesson
|
|
411
|
+
* carries `data-edition` (`chapter` or `slides`), `data-lesson` (the
|
|
412
|
+
* lesson's slug) and `data-title` (its title) in `attrs`, so a chapter is
|
|
413
|
+
* `{ slug, attrs }` where it was a bare slug. An item lists its lesson when
|
|
414
|
+
* it is served at the lesson's own URL, `data-lesson` equal to its `slug`:
|
|
415
|
+
* a chapter, or a slides-first lesson's deck; a chapter's slides row
|
|
416
|
+
* follows it as before.
|
|
417
|
+
*
|
|
418
|
+
* v35 (the slides a reader has seen): every frame of `decks[].frames`
|
|
419
|
+
* carries `id`, 12 hex digits of a SHA-256 over what it shows, which a
|
|
420
|
+
* reader's record of the frames shown in Present mode keeps
|
|
421
|
+
* (`ManifestDeckFrame.id`).
|
|
422
|
+
*
|
|
423
|
+
* v36 (a part's deck): `decks` lists each part's deck
|
|
424
|
+
* (`<part>/index.deck.mdx`, at `<part>/slides`) too, with `part` (its
|
|
425
|
+
* directory) and no `lessonId`, which is now optional. Every deck carries
|
|
426
|
+
* `covers`, the chapters a part's deck covers with the frame each one's
|
|
427
|
+
* section opens on (empty on a lesson's deck); a part's deck carries
|
|
428
|
+
* `recalledFrom`, the chapter of each float it recalls, and each frame of
|
|
429
|
+
* a covered section `chapter`. In `sidebar`, a part's deck follows the
|
|
430
|
+
* part's Overview as `{ slug, label: "Slides", attrs: { class:
|
|
431
|
+
* "explico-deck-row explico-part-deck", "data-edition": "slides",
|
|
432
|
+
* "data-part" } }`, and a chapter it covers that has no deck of its own
|
|
433
|
+
* carries `data-deck-href`, the address of its section.
|
|
434
|
+
*
|
|
435
|
+
* v37 (the page's zones and slide edges): `appearance` carries the course's
|
|
436
|
+
* `appearance` setting, defaults applied: `zones` (`tonal`, `accent-header`,
|
|
437
|
+
* `paper` or `none`) and `slides` (`lifted`, `edge` or `sorter`).
|
|
349
438
|
*/
|
|
350
|
-
export const MANIFEST_SCHEMA_VERSION =
|
|
439
|
+
export const MANIFEST_SCHEMA_VERSION = 37;
|
|
351
440
|
|
|
352
441
|
const REPOSITORY_ROOT = process.cwd();
|
|
353
442
|
// Content is the top-level `content/` folder; the build output is `build/`
|
|
@@ -453,6 +542,17 @@ export interface ManifestDerivedNotation {
|
|
|
453
542
|
readonly formulaScope: readonly GlyphScopeEntry[];
|
|
454
543
|
}
|
|
455
544
|
|
|
545
|
+
/**
|
|
546
|
+
* One lesson's deck (v33), or a part's (v36): its frames as `decks.ts` reads
|
|
547
|
+
* them, and the notation its page shows — its own bundle, which holds its
|
|
548
|
+
* chapters'.
|
|
549
|
+
*/
|
|
550
|
+
export interface ManifestDeck extends BuiltDeck {
|
|
551
|
+
readonly notation: ManifestLesson['notation'];
|
|
552
|
+
/** The version the deck's page last changed in (rule C5), as a chapter's `since`. */
|
|
553
|
+
readonly since: string | null;
|
|
554
|
+
}
|
|
555
|
+
|
|
456
556
|
/**
|
|
457
557
|
* One keyed float of a lesson (v21): its key and number, and its caption pair
|
|
458
558
|
* — `short` (an equation's `\caption{…}`, or the `short` prop) and, for a
|
|
@@ -484,6 +584,12 @@ export interface ManifestLesson {
|
|
|
484
584
|
readonly id: string;
|
|
485
585
|
/** Docs-loader slug, e.g. `bonds/price-yield-relationship`. */
|
|
486
586
|
readonly slug: string;
|
|
587
|
+
/**
|
|
588
|
+
* The lesson's editions (v33): `chapter` (its `.mdx`), `deck` (its
|
|
589
|
+
* `.deck.mdx`, listed in `manifest.decks`), or both. A slides-first lesson
|
|
590
|
+
* is `["deck"]`: its slug is its deck's route and its floats are unnumbered.
|
|
591
|
+
*/
|
|
592
|
+
readonly editions: readonly ('chapter' | 'deck')[];
|
|
487
593
|
/**
|
|
488
594
|
* The 1-based ordinal of the **part** this lesson sits in — its sidebar
|
|
489
595
|
* group's position in `courseConfig.parts`. It is the first number of every
|
|
@@ -532,6 +638,15 @@ export interface ManifestLesson {
|
|
|
532
638
|
readonly file: string;
|
|
533
639
|
readonly title: string;
|
|
534
640
|
readonly description: string;
|
|
641
|
+
/**
|
|
642
|
+
* The knowledge version this chapter's content last changed in (v32, rule
|
|
643
|
+
* C5): its file, its checks and assessments, the shared notation it uses,
|
|
644
|
+
* the sources it cites and its numbers, against `content/versions.json`.
|
|
645
|
+
* `null` while it has a change `## [Unreleased]` has not released. What
|
|
646
|
+
* the exports and the `[?]` panel print for the page; with rule C5 off,
|
|
647
|
+
* the course's own version.
|
|
648
|
+
*/
|
|
649
|
+
readonly since: string | null;
|
|
535
650
|
/** Authored reader-facing estimate; `40` and `[30, 40]` both land here. */
|
|
536
651
|
readonly studyMinutes?: { readonly min: number; readonly max: number };
|
|
537
652
|
/** Ordered — the curriculum contract. */
|
|
@@ -614,6 +729,13 @@ export interface ManifestAbout {
|
|
|
614
729
|
readonly date?: string;
|
|
615
730
|
/** Bullet notes from that entry. */
|
|
616
731
|
readonly notes: readonly string[];
|
|
732
|
+
/**
|
|
733
|
+
* Every released entry, newest first, with its date (v32): what dates a
|
|
734
|
+
* document whose content last changed in an earlier release (rule C5).
|
|
735
|
+
*/
|
|
736
|
+
readonly releases: readonly { readonly version: string; readonly date?: string }[];
|
|
737
|
+
/** `## [Unreleased]` holds bullets: a change is waiting for a release (v32). */
|
|
738
|
+
readonly unreleased: boolean;
|
|
617
739
|
/** Content fingerprint at compile time — ties the shown version to a build. */
|
|
618
740
|
readonly contentTree: string;
|
|
619
741
|
};
|
|
@@ -677,6 +799,8 @@ export interface ManifestPart {
|
|
|
677
799
|
readonly title: string;
|
|
678
800
|
readonly file: string;
|
|
679
801
|
readonly description?: string;
|
|
802
|
+
/** The version the page's content last changed in (v32, rule C5), as a chapter's `since`. */
|
|
803
|
+
readonly since: string | null;
|
|
680
804
|
};
|
|
681
805
|
}
|
|
682
806
|
|
|
@@ -692,6 +816,30 @@ export interface Manifest {
|
|
|
692
816
|
readonly blockLabels: BlockLabels;
|
|
693
817
|
/** How the explanation cards start for a reader, defaults applied (`CourseConfig.cards`). */
|
|
694
818
|
readonly cards: NormalizedCards;
|
|
819
|
+
/**
|
|
820
|
+
* Which marks the sidebar shows a reader beside a chapter or part page
|
|
821
|
+
* (v32, `CourseConfig.sidebarMarks`): not opened yet, changed since opened.
|
|
822
|
+
*/
|
|
823
|
+
readonly sidebarMarks: NormalizedSidebarMarks;
|
|
824
|
+
/**
|
|
825
|
+
* Which edition a lesson's sidebar row opens for a reader who has not
|
|
826
|
+
* chosen, and whether it lists the other (v34, `CourseConfig.sidebarEditions`).
|
|
827
|
+
*/
|
|
828
|
+
readonly sidebarEditions: NormalizedSidebarEditions;
|
|
829
|
+
/**
|
|
830
|
+
* Whether the course asks the reader to keep their reading memory (v32,
|
|
831
|
+
* `CourseConfig.readingMemory`): `ask` or `off`.
|
|
832
|
+
*/
|
|
833
|
+
readonly readingMemory: ReadingMemorySetting;
|
|
834
|
+
/**
|
|
835
|
+
* How the page tells its zones apart and how slides sit on it, defaults
|
|
836
|
+
* applied (v37, `CourseConfig.appearance`).
|
|
837
|
+
*/
|
|
838
|
+
readonly appearance: NormalizedAppearance;
|
|
839
|
+
/** The course's `deck` settings, defaults applied, and its canvas (v33). */
|
|
840
|
+
readonly deck: NormalizedDeck;
|
|
841
|
+
/** Each lesson's deck, by route (v33). */
|
|
842
|
+
readonly decks: readonly ManifestDeck[];
|
|
695
843
|
/** Reader-facing identity + versions (the `[?]` About panel). */
|
|
696
844
|
readonly about: ManifestAbout;
|
|
697
845
|
readonly notation: {
|
|
@@ -711,6 +859,13 @@ export interface Manifest {
|
|
|
711
859
|
/** The course's parts in config order, with their chapters and overview pages. */
|
|
712
860
|
readonly parts: readonly ManifestPart[];
|
|
713
861
|
readonly lessons: readonly ManifestLesson[];
|
|
862
|
+
/**
|
|
863
|
+
* The record of every chapter's and part page's own version (v32, rule
|
|
864
|
+
* C5), as `validate:content` writes it to `content/versions.json`
|
|
865
|
+
* (`syncPageVersions`); `lessons[].since` and `parts[].overview.since`
|
|
866
|
+
* are read from it.
|
|
867
|
+
*/
|
|
868
|
+
readonly pageVersions: ManifestPageVersions;
|
|
714
869
|
/** Every docs page that is not a chapter, by id (v25). */
|
|
715
870
|
readonly pages: readonly ManifestPage[];
|
|
716
871
|
/** Full competency prerequisite graph. */
|
|
@@ -872,11 +1027,12 @@ function lessonNotationResolution(
|
|
|
872
1027
|
definitionsById: ReadonlyMap<string, NotationDefinitionRecord>,
|
|
873
1028
|
availableDefinitionIds: ReadonlySet<string>,
|
|
874
1029
|
): ManifestNotationResolution[] {
|
|
875
|
-
|
|
1030
|
+
// A deck's references resolve in its lessons' scopes, as the registry resolves them.
|
|
1031
|
+
const scopes = pageScopeLessonIds(lesson);
|
|
876
1032
|
return lesson.references.map((reference) => {
|
|
877
|
-
const definitionId =
|
|
1033
|
+
const definitionId = resolvePageReferenceId(
|
|
878
1034
|
reference.key,
|
|
879
|
-
|
|
1035
|
+
scopes,
|
|
880
1036
|
availableDefinitionIds,
|
|
881
1037
|
);
|
|
882
1038
|
const definition =
|
|
@@ -982,7 +1138,6 @@ function lessonFrontmatterById(): Map<
|
|
|
982
1138
|
studyMinutes?: { min: number; max: number };
|
|
983
1139
|
}
|
|
984
1140
|
> {
|
|
985
|
-
const base = join(CONTENT_ROOT, 'docs');
|
|
986
1141
|
const result = new Map<
|
|
987
1142
|
string,
|
|
988
1143
|
{
|
|
@@ -991,29 +1146,18 @@ function lessonFrontmatterById(): Map<
|
|
|
991
1146
|
studyMinutes?: { min: number; max: number };
|
|
992
1147
|
}
|
|
993
1148
|
>();
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
{}) as Record<string, unknown>;
|
|
1007
|
-
const studyMinutes = studyMinutesRange(data.studyMinutes);
|
|
1008
|
-
result.set(id, {
|
|
1009
|
-
title: typeof data.title === 'string' ? data.title : id,
|
|
1010
|
-
description:
|
|
1011
|
-
typeof data.description === 'string' ? data.description : '',
|
|
1012
|
-
...(studyMinutes === undefined ? {} : { studyMinutes }),
|
|
1013
|
-
});
|
|
1014
|
-
}
|
|
1015
|
-
};
|
|
1016
|
-
walk(base);
|
|
1149
|
+
// Every lesson's defining file: its chapter, or the deck of a slides-first lesson.
|
|
1150
|
+
for (const { path, lessonId: id } of loadLessonFiles()) {
|
|
1151
|
+
const data = (parseFrontmatter(readFileSync(path, 'utf8')).frontmatter ??
|
|
1152
|
+
{}) as Record<string, unknown>;
|
|
1153
|
+
const studyMinutes = studyMinutesRange(data.studyMinutes);
|
|
1154
|
+
result.set(id, {
|
|
1155
|
+
title: typeof data.title === 'string' ? data.title : id,
|
|
1156
|
+
description:
|
|
1157
|
+
typeof data.description === 'string' ? data.description : '',
|
|
1158
|
+
...(studyMinutes === undefined ? {} : { studyMinutes }),
|
|
1159
|
+
});
|
|
1160
|
+
}
|
|
1017
1161
|
return result;
|
|
1018
1162
|
}
|
|
1019
1163
|
|
|
@@ -1050,6 +1194,7 @@ export async function compileManifest(
|
|
|
1050
1194
|
// Throws on a bad `numbering` or `blockLabels` value, like `resolveRules` above.
|
|
1051
1195
|
const numberingSettings = normalizeNumbering(courseConfig);
|
|
1052
1196
|
const blockLabels = normalizeBlockLabels(courseConfig);
|
|
1197
|
+
const deckSettings = normalizeDeck(courseConfig);
|
|
1053
1198
|
|
|
1054
1199
|
const [catalog, notationInput, abbreviationsInput] = await Promise.all([
|
|
1055
1200
|
loadCurriculumCatalog(),
|
|
@@ -1062,11 +1207,41 @@ export async function compileManifest(
|
|
|
1062
1207
|
// text and math pass the same gates. It stays out of `notationInput.lessons`,
|
|
1063
1208
|
// which is the chapter list every numbering and curriculum pass reads.
|
|
1064
1209
|
const nonChapterPages = loadNonChapterPages();
|
|
1210
|
+
// A deck beside its chapter is a page of its own with its chapter's scope
|
|
1211
|
+
// (`loadDeckNotationPages`), and a part's deck one with its chapters': its
|
|
1212
|
+
// text and math pass the same gates. A slides-first lesson's deck is the
|
|
1213
|
+
// lesson itself, already in `notationInput.lessons`; it numbers nothing,
|
|
1214
|
+
// so the chapter-only passes below (`chapterInputs`) leave it out.
|
|
1215
|
+
const deckFiles = loadDeckFiles();
|
|
1216
|
+
const lessonDeckFiles = deckFiles.filter((deck): deck is LessonDeckFile => deck.scope === 'lesson');
|
|
1217
|
+
// A part's lessons in the order the tracks read them, as the sidebar
|
|
1218
|
+
// lists them: a part's deck binds a key its chapters define to the first.
|
|
1219
|
+
const trackRank = new Map(
|
|
1220
|
+
mergeTrackOrder(catalog.tracks.map((track) => ({ id: track.id, lessons: track.lessons }))).map(
|
|
1221
|
+
(lessonId, index) => [lessonId, index],
|
|
1222
|
+
),
|
|
1223
|
+
);
|
|
1224
|
+
const readingOrder = (lessonIds: readonly string[]): string[] =>
|
|
1225
|
+
[...lessonIds].sort(
|
|
1226
|
+
(left, right) =>
|
|
1227
|
+
(trackRank.get(left) ?? Number.POSITIVE_INFINITY) - (trackRank.get(right) ?? Number.POSITIVE_INFINITY) ||
|
|
1228
|
+
left.localeCompare(right),
|
|
1229
|
+
);
|
|
1230
|
+
const deckPages = loadDeckNotationPages(deckFiles, { order: readingOrder });
|
|
1231
|
+
const deckLessonIds = new Set(
|
|
1232
|
+
loadLessonFiles()
|
|
1233
|
+
.filter((file) => file.edition === 'deck')
|
|
1234
|
+
.map((file) => file.lessonId),
|
|
1235
|
+
);
|
|
1236
|
+
const isChapter = (lessonId: string): boolean => !deckLessonIds.has(lessonId);
|
|
1237
|
+
const deckPageIds = new Set(deckPages.map((page) => page.notation.lessonId));
|
|
1238
|
+
const chapterInputs = notationInput.lessons.filter((lesson) => isChapter(lesson.lessonId));
|
|
1065
1239
|
const withPages: typeof notationInput = {
|
|
1066
1240
|
...notationInput,
|
|
1067
1241
|
lessons: [
|
|
1068
1242
|
...notationInput.lessons,
|
|
1069
1243
|
...nonChapterPages.map((page) => page.notation),
|
|
1244
|
+
...deckPages.map((page) => page.notation),
|
|
1070
1245
|
],
|
|
1071
1246
|
};
|
|
1072
1247
|
const registry = buildNotationRegistry(withPages);
|
|
@@ -1120,9 +1295,9 @@ export async function compileManifest(
|
|
|
1120
1295
|
notationInput.sharedDefinitions.map((definition) => [definition.key, definition.label]),
|
|
1121
1296
|
);
|
|
1122
1297
|
const localLabelsByLesson = new Map(
|
|
1123
|
-
|
|
1298
|
+
withPages.lessons.map((lesson) => [
|
|
1124
1299
|
lesson.lessonId,
|
|
1125
|
-
new Map(lesson.
|
|
1300
|
+
new Map(scopeLocals(lesson).map((definition) => [definition.key, definition.label])),
|
|
1126
1301
|
]),
|
|
1127
1302
|
);
|
|
1128
1303
|
const termLabel = (lessonId: string, key: string): string | undefined =>
|
|
@@ -1178,19 +1353,27 @@ export async function compileManifest(
|
|
|
1178
1353
|
})
|
|
1179
1354
|
: []),
|
|
1180
1355
|
...(rules.isEnabled('S1')
|
|
1181
|
-
? gateHeadingLength(
|
|
1356
|
+
? gateHeadingLength(chapterInputs, {
|
|
1182
1357
|
maxLength: rules.number('S1', 'maxLength'),
|
|
1183
1358
|
labelFor: termLabel,
|
|
1184
1359
|
})
|
|
1185
1360
|
: []),
|
|
1186
|
-
...gateHeadingStructure(
|
|
1361
|
+
...gateHeadingStructure(chapterInputs, {
|
|
1187
1362
|
levels: rules.isEnabled('S6'),
|
|
1188
1363
|
adjacency: rules.isEnabled('S7'),
|
|
1189
1364
|
}),
|
|
1190
1365
|
// The course's own rules (`courseRules`): one pass for every enabled `X`
|
|
1191
1366
|
// rule that carries a `forbid` pattern. A disabled one, and a guide rule
|
|
1192
1367
|
// with no pattern, do no work here.
|
|
1193
|
-
|
|
1368
|
+
// A course rule about one edition is checked on that edition's pages.
|
|
1369
|
+
...gateCourseRules(
|
|
1370
|
+
withPages.lessons.filter((lesson) => isChapter(lesson.lessonId) && !deckPageIds.has(lesson.lessonId)),
|
|
1371
|
+
rules.course.filter((rule) => rule.editions !== 'deck'),
|
|
1372
|
+
),
|
|
1373
|
+
...gateCourseRules(
|
|
1374
|
+
withPages.lessons.filter((lesson) => !isChapter(lesson.lessonId) || deckPageIds.has(lesson.lessonId)),
|
|
1375
|
+
rules.course.filter((rule) => rule.editions !== 'chapter'),
|
|
1376
|
+
),
|
|
1194
1377
|
// S10 is locked: a float on a non-chapter page has no number to render.
|
|
1195
1378
|
...gateNonChapterPages(nonChapterPages),
|
|
1196
1379
|
];
|
|
@@ -1311,6 +1494,17 @@ export async function compileManifest(
|
|
|
1311
1494
|
'is left out of the sidebar and the numbering until it has one.',
|
|
1312
1495
|
});
|
|
1313
1496
|
}
|
|
1497
|
+
// A part's deck, of a part the sidebar lists: one in a directory no part
|
|
1498
|
+
// lists is `deck-outside-part` (`decks-validate.ts`).
|
|
1499
|
+
const configuredDirs = new Set(configuredParts.map((part) => part.dir));
|
|
1500
|
+
const partDeckFiles = deckFiles.filter(
|
|
1501
|
+
(deck): deck is PartDeckFile => deck.scope === 'part' && configuredDirs.has(deck.partDir),
|
|
1502
|
+
);
|
|
1503
|
+
/** A part's chapters, by their names in its directory (what `[chapter: <name>]` writes). */
|
|
1504
|
+
const partChapterNames = (dir: string): string[] =>
|
|
1505
|
+
lessonSlugs
|
|
1506
|
+
.filter((slug) => slug.startsWith(`${dir}/`) && isChapter(lessonIdFromSlug(slug)))
|
|
1507
|
+
.map((slug) => slug.slice(dir.length + 1));
|
|
1314
1508
|
// A lesson whose directory no part lists has no part, no chapter number and
|
|
1315
1509
|
// no place in the sidebar.
|
|
1316
1510
|
const partDirs = new Set(listedParts.map((part) => part.dir));
|
|
@@ -1339,21 +1533,6 @@ export async function compileManifest(
|
|
|
1339
1533
|
),
|
|
1340
1534
|
);
|
|
1341
1535
|
}
|
|
1342
|
-
// Per-place waivers: drop the located findings each one covers, and
|
|
1343
|
-
// report the waivers themselves — a stale one is a warning, a bad one an
|
|
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.
|
|
1347
|
-
const waiverDiagnostics: WaiverDiagnostic[] = [];
|
|
1348
|
-
const waiverRules = { courseRules: rules.course };
|
|
1349
|
-
const waivers = withPages.lessons.flatMap((lesson) => {
|
|
1350
|
-
const collected = collectWaivers(lesson, waiverRules);
|
|
1351
|
-
waiverDiagnostics.push(...collected.diagnostics);
|
|
1352
|
-
return collected.waivers;
|
|
1353
|
-
});
|
|
1354
|
-
const waived = applyWaivers(mathIssues, waivers, waiverRules);
|
|
1355
|
-
mathIssues.splice(0, mathIssues.length, ...waived.kept);
|
|
1356
|
-
waiverDiagnostics.push(...waived.unused);
|
|
1357
1536
|
|
|
1358
1537
|
const sidebar = buildSidebar(
|
|
1359
1538
|
catalog.tracks.map((track) => ({ id: track.id, lessons: track.lessons })),
|
|
@@ -1368,6 +1547,34 @@ export async function compileManifest(
|
|
|
1368
1547
|
floatIndex: ['.mdx', '.md'].some((extension) =>
|
|
1369
1548
|
existsSync(join(CONTENT_ROOT, 'docs', `figures-and-tables${extension}`)),
|
|
1370
1549
|
),
|
|
1550
|
+
// A deck beside its chapter is listed right under it.
|
|
1551
|
+
decks: new Map(
|
|
1552
|
+
lessonDeckFiles
|
|
1553
|
+
.filter((deck) => deck.chapterPath !== undefined && deck.inPart)
|
|
1554
|
+
.map((deck) => [deck.lessonSlug, deck.route]),
|
|
1555
|
+
),
|
|
1556
|
+
// A deck with no chapter is the lesson: its row carries the deck's glyph.
|
|
1557
|
+
slidesFirst: new Set(
|
|
1558
|
+
lessonDeckFiles
|
|
1559
|
+
.filter((deck) => deck.chapterPath === undefined && deck.inPart)
|
|
1560
|
+
.map((deck) => deck.lessonSlug),
|
|
1561
|
+
),
|
|
1562
|
+
// A part's deck follows the part's Overview; a chapter it covers that
|
|
1563
|
+
// has no deck of its own links into it, at its section's divider.
|
|
1564
|
+
partDecks: new Map(
|
|
1565
|
+
partDeckFiles.map((deck) => {
|
|
1566
|
+
const { covered } = coveredSections(readDeckSource(deck.body), new Set(partChapterNames(deck.partDir)));
|
|
1567
|
+
return [
|
|
1568
|
+
deck.partDir,
|
|
1569
|
+
{
|
|
1570
|
+
route: deck.route,
|
|
1571
|
+
covers: new Map(covered.map(({ name, position }) => [`${deck.partDir}/${name}`, position + 2])),
|
|
1572
|
+
},
|
|
1573
|
+
];
|
|
1574
|
+
}),
|
|
1575
|
+
),
|
|
1576
|
+
// Each edition's row names its lesson, which the sidebar's own row shows.
|
|
1577
|
+
titleOf: (slug) => frontmatterById.get(lessonIdFromSlug(slug))?.title,
|
|
1371
1578
|
},
|
|
1372
1579
|
);
|
|
1373
1580
|
|
|
@@ -1397,9 +1604,10 @@ export async function compileManifest(
|
|
|
1397
1604
|
// after them is no part. An overview-only part counts, as `parts[]` does.
|
|
1398
1605
|
for (const group of sidebar.slice(0, configuredParts.length)) {
|
|
1399
1606
|
partCursor += 1;
|
|
1400
|
-
const lessonIds = group.items
|
|
1401
|
-
|
|
1402
|
-
|
|
1607
|
+
const lessonIds = group.items.flatMap((item) => {
|
|
1608
|
+
const slug = sidebarLessonSlug(item);
|
|
1609
|
+
return slug === undefined ? [] : [lessonIdFromSlug(slug)];
|
|
1610
|
+
});
|
|
1403
1611
|
if (lessonIds.length === 0) continue;
|
|
1404
1612
|
// Running totals for this part, advanced one chapter at a time; they
|
|
1405
1613
|
// only reach a number under the part scope.
|
|
@@ -1413,7 +1621,8 @@ export async function compileManifest(
|
|
|
1413
1621
|
};
|
|
1414
1622
|
let chapterCursor = 0;
|
|
1415
1623
|
for (const lessonId of lessonIds) {
|
|
1416
|
-
|
|
1624
|
+
// A slides-first lesson's floats are unnumbered (rule D8): it adds none.
|
|
1625
|
+
const body = isChapter(lessonId) ? (bodyByLessonId.get(lessonId) ?? '') : '';
|
|
1417
1626
|
const chapter = ++chapterCursor;
|
|
1418
1627
|
placementByLessonId.set(lessonId, {
|
|
1419
1628
|
part: partCursor,
|
|
@@ -1448,7 +1657,8 @@ export async function compileManifest(
|
|
|
1448
1657
|
return [
|
|
1449
1658
|
lesson.lessonId,
|
|
1450
1659
|
scanSectionHeadings(lesson.body, {
|
|
1451
|
-
|
|
1660
|
+
// A deck's headings are sections and frames, numbered as frames.
|
|
1661
|
+
...(placement && isChapter(lesson.lessonId)
|
|
1452
1662
|
? { prefix: `${placement.part}.${placement.chapter}` }
|
|
1453
1663
|
: {}),
|
|
1454
1664
|
depth: numberingSettings.sections.depth,
|
|
@@ -1475,7 +1685,11 @@ export async function compileManifest(
|
|
|
1475
1685
|
// The parts themselves, in config order, with their chapters in reading
|
|
1476
1686
|
// order — the one place a part-level page or a breadcrumb reads, so neither
|
|
1477
1687
|
// has to re-derive a part from lesson slugs.
|
|
1478
|
-
|
|
1688
|
+
// A part's overview page gets its `since` with the lessons, below.
|
|
1689
|
+
type PartBeforeVersions = Omit<ManifestPart, 'overview'> & {
|
|
1690
|
+
readonly overview?: Omit<NonNullable<ManifestPart['overview']>, 'since'>;
|
|
1691
|
+
};
|
|
1692
|
+
const manifestParts: PartBeforeVersions[] = configuredParts.map((part, index) => {
|
|
1479
1693
|
const overview = overviewByDir.get(part.dir);
|
|
1480
1694
|
const lessonIds = [...placementByLessonId]
|
|
1481
1695
|
.filter(([lessonId]) => lessonId.startsWith(`${part.dir}.`))
|
|
@@ -1520,7 +1734,7 @@ export async function compileManifest(
|
|
|
1520
1734
|
const floatInputs = (
|
|
1521
1735
|
kind: 'equations' | 'tables' | 'figures' | 'diagrams' | 'examples',
|
|
1522
1736
|
) =>
|
|
1523
|
-
|
|
1737
|
+
chapterInputs.map((lesson) => {
|
|
1524
1738
|
const placement = placementByLessonId.get(lesson.lessonId);
|
|
1525
1739
|
return {
|
|
1526
1740
|
lessonId: lesson.lessonId,
|
|
@@ -1658,6 +1872,8 @@ export async function compileManifest(
|
|
|
1658
1872
|
// `<AssessmentSet />` draws every check of the lesson, or the one its `id`
|
|
1659
1873
|
// names; `<CompactExamples>` draws its own set.
|
|
1660
1874
|
const titledBlocksFor = (lessonId: string): ManifestTitledBlock[] => {
|
|
1875
|
+
// A deck's question and example frames are frames, listed as frames.
|
|
1876
|
+
if (!isChapter(lessonId)) return [];
|
|
1661
1877
|
const slug = lessonId.replace(/\./g, '/');
|
|
1662
1878
|
const body = bodyByLessonId.get(lessonId) ?? '';
|
|
1663
1879
|
const headings = headingsByLessonId.get(lessonId) ?? [];
|
|
@@ -1688,7 +1904,7 @@ export async function compileManifest(
|
|
|
1688
1904
|
// Rule V3: the course files a lesson's imports reach draw math through
|
|
1689
1905
|
// `renderLabMath`, never KaTeX itself.
|
|
1690
1906
|
const labKatexDiagnostics = validateLabKatexImports(
|
|
1691
|
-
notationInput.lessons.map((lesson) => ({
|
|
1907
|
+
[...notationInput.lessons, ...deckPages.map((page) => page.notation)].map((lesson) => ({
|
|
1692
1908
|
lessonId: lesson.lessonId,
|
|
1693
1909
|
file: lesson.source.file,
|
|
1694
1910
|
body: lesson.body,
|
|
@@ -1696,7 +1912,7 @@ export async function compileManifest(
|
|
|
1696
1912
|
REPOSITORY_ROOT,
|
|
1697
1913
|
);
|
|
1698
1914
|
const rawFloatDiagnostics = validateRawFloats(
|
|
1699
|
-
notationInput.lessons.map((lesson) => ({
|
|
1915
|
+
[...notationInput.lessons, ...deckPages.map((page) => page.notation)].map((lesson) => ({
|
|
1700
1916
|
lessonId: lesson.lessonId,
|
|
1701
1917
|
slug: lesson.lessonId.replace(/\./g, '/'),
|
|
1702
1918
|
file: lesson.source.file,
|
|
@@ -1704,7 +1920,124 @@ export async function compileManifest(
|
|
|
1704
1920
|
})),
|
|
1705
1921
|
);
|
|
1706
1922
|
|
|
1707
|
-
|
|
1923
|
+
// The decks (rules D1–D10 and the `decks` invariant): each read as frames,
|
|
1924
|
+
// against its page's scope and its chapter's numbers.
|
|
1925
|
+
const pageInputById = new Map(withPages.lessons.map((lesson) => [lesson.lessonId, lesson]));
|
|
1926
|
+
const chapterBodyById = new Map(chapterInputs.map((lesson) => [lesson.lessonId, lesson.body]));
|
|
1927
|
+
const floatNumbers = (floats: readonly { readonly key: string; readonly number: string }[] | undefined) =>
|
|
1928
|
+
new Map((floats ?? []).map((float) => [float.key, float.number]));
|
|
1929
|
+
// A competency's `outcome` (`content-schemas.ts`), which the outcomes frame lists.
|
|
1930
|
+
const outcomeById = new Map(
|
|
1931
|
+
catalog.competencies.flatMap((competency): [string, string][] => {
|
|
1932
|
+
const outcome = (competency as { readonly outcome?: unknown }).outcome;
|
|
1933
|
+
return typeof outcome === 'string' ? [[competency.id, outcome]] : [];
|
|
1934
|
+
}),
|
|
1935
|
+
);
|
|
1936
|
+
/** What a chapter numbers, which a deck may recall: each float's number, an equation's LaTeX. */
|
|
1937
|
+
const chapterFloatsOf = (lessonId: string): ChapterFloats => {
|
|
1938
|
+
const slug = lessonId.replace(/\./g, '/');
|
|
1939
|
+
const chapterBody = chapterBodyById.get(lessonId);
|
|
1940
|
+
const latex = chapterBody === undefined ? new Map<string, string>() : chapterEquationLatex(chapterBody);
|
|
1941
|
+
return {
|
|
1942
|
+
equations: new Map(
|
|
1943
|
+
(equationsBySlug.get(slug) ?? []).map((equation) => [
|
|
1944
|
+
equation.key,
|
|
1945
|
+
{ number: equation.number, ...(latex.has(equation.key) ? { latex: latex.get(equation.key) } : {}) },
|
|
1946
|
+
]),
|
|
1947
|
+
),
|
|
1948
|
+
figures: floatNumbers(figuresBySlug.get(slug)),
|
|
1949
|
+
tables: floatNumbers(tablesBySlug.get(slug)),
|
|
1950
|
+
diagrams: floatNumbers(diagramsBySlug.get(slug)),
|
|
1951
|
+
};
|
|
1952
|
+
};
|
|
1953
|
+
const outcomesOf = (lessonId: string): string[] =>
|
|
1954
|
+
(lessonMetaById.get(lessonId)?.teaches ?? []).flatMap((id) => {
|
|
1955
|
+
const outcome = outcomeById.get(id);
|
|
1956
|
+
return outcome ? [outcome] : [];
|
|
1957
|
+
});
|
|
1958
|
+
// Each part with a deck: its chapters in reading order, what each numbers
|
|
1959
|
+
// and teaches; its slides-first lessons, which no section can cover.
|
|
1960
|
+
const deckParts = new Map<string, DeckPartFacts>(
|
|
1961
|
+
partDeckFiles.map((deck) => {
|
|
1962
|
+
const part = manifestParts.find((entry) => entry.dir === deck.partDir);
|
|
1963
|
+
const lessonIds = part?.lessons ?? [];
|
|
1964
|
+
const nameOf = (lessonId: string) => lessonId.replace(/\./g, '/').slice(deck.partDir.length + 1);
|
|
1965
|
+
return [
|
|
1966
|
+
deck.partDir,
|
|
1967
|
+
{
|
|
1968
|
+
label: part?.label ?? deck.partDir,
|
|
1969
|
+
chapters: lessonIds.filter(isChapter).map((lessonId) => ({
|
|
1970
|
+
name: nameOf(lessonId),
|
|
1971
|
+
slug: lessonId.replace(/\./g, '/'),
|
|
1972
|
+
teaches: lessonMetaById.get(lessonId)?.teaches ?? [],
|
|
1973
|
+
outcomes: outcomesOf(lessonId),
|
|
1974
|
+
floats: chapterFloatsOf(lessonId),
|
|
1975
|
+
})),
|
|
1976
|
+
slidesFirst: lessonIds.filter((lessonId) => !isChapter(lessonId)).map(nameOf),
|
|
1977
|
+
},
|
|
1978
|
+
];
|
|
1979
|
+
}),
|
|
1980
|
+
);
|
|
1981
|
+
const deckModel = buildDecks({
|
|
1982
|
+
decks: deckFiles,
|
|
1983
|
+
settings: deckSettings,
|
|
1984
|
+
rules: {
|
|
1985
|
+
maxWords: rules.number('D2', 'maxWords'),
|
|
1986
|
+
maxBullets: rules.number('D2', 'maxBullets'),
|
|
1987
|
+
maxTitleLength: rules.number('D5', 'maxLength'),
|
|
1988
|
+
maxSteps: rules.number('D6', 'maxSteps'),
|
|
1989
|
+
maxNewSymbols: rules.number('D9', 'maxNewSymbols'),
|
|
1990
|
+
},
|
|
1991
|
+
lessons: new Map(
|
|
1992
|
+
lessonDeckFiles.map((deck) => [
|
|
1993
|
+
deck.lessonId,
|
|
1994
|
+
{
|
|
1995
|
+
title: frontmatterById.get(deck.lessonId)?.title ?? deck.lessonId,
|
|
1996
|
+
teaches: lessonMetaById.get(deck.lessonId)?.teaches ?? [],
|
|
1997
|
+
outcomes: outcomesOf(deck.lessonId),
|
|
1998
|
+
...(deck.chapterPath === undefined ? {} : { chapterFloats: chapterFloatsOf(deck.lessonId) }),
|
|
1999
|
+
},
|
|
2000
|
+
]),
|
|
2001
|
+
),
|
|
2002
|
+
parts: deckParts,
|
|
2003
|
+
// As the title frame shows them (`titleFrameNode` in `deck-remark.mjs`).
|
|
2004
|
+
course: {
|
|
2005
|
+
name: courseConfig.about.materialName ?? courseConfig.title,
|
|
2006
|
+
authors: courseConfig.about.authors.map((author) => author.name),
|
|
2007
|
+
},
|
|
2008
|
+
scopeOf: (pageId) => {
|
|
2009
|
+
const page = pageInputById.get(pageId);
|
|
2010
|
+
return page === undefined ? [] : pageScope(page, cardsByKey);
|
|
2011
|
+
},
|
|
2012
|
+
suspendedOf: (pageId) => {
|
|
2013
|
+
const page = pageInputById.get(pageId);
|
|
2014
|
+
return page === undefined ? [] : suspendedOperators(page);
|
|
2015
|
+
},
|
|
2016
|
+
labelFor: termLabel,
|
|
2017
|
+
});
|
|
2018
|
+
mathIssues.push(...deckModel.diagnostics);
|
|
2019
|
+
// The part's deck and each chapter's section of it, as the decks number their frames.
|
|
2020
|
+
const sidebarWithFrames = withPartDeckFrames(sidebar, deckModel.decks);
|
|
2021
|
+
|
|
2022
|
+
// Per-place waivers: drop the located findings each one covers, and
|
|
2023
|
+
// report the waivers themselves — a stale one is a warning, a bad one an
|
|
2024
|
+
// error. Chapters, decks and non-chapter pages are scanned alike, since the
|
|
2025
|
+
// text and math gates run on all of them; S2 and S10 are per-file or
|
|
2026
|
+
// locked, so no waiver reaches them. Applied once every located finding is
|
|
2027
|
+
// in, the deck rules' included.
|
|
2028
|
+
const waiverDiagnostics: WaiverDiagnostic[] = [];
|
|
2029
|
+
const waiverRules = { courseRules: rules.course };
|
|
2030
|
+
const waivers = withPages.lessons.flatMap((lesson) => {
|
|
2031
|
+
const collected = collectWaivers(lesson, waiverRules);
|
|
2032
|
+
waiverDiagnostics.push(...collected.diagnostics);
|
|
2033
|
+
return collected.waivers;
|
|
2034
|
+
});
|
|
2035
|
+
const waived = applyWaivers(mathIssues, waivers, waiverRules);
|
|
2036
|
+
mathIssues.splice(0, mathIssues.length, ...waived.kept);
|
|
2037
|
+
waiverDiagnostics.push(...waived.unused);
|
|
2038
|
+
|
|
2039
|
+
// Each lesson gets its `since` below, once every page's inputs are known.
|
|
2040
|
+
const lessons: Omit<ManifestLesson, 'since'>[] = [];
|
|
1708
2041
|
for (const notationLesson of notationInput.lessons) {
|
|
1709
2042
|
const id = notationLesson.lessonId;
|
|
1710
2043
|
const meta = lessonMetaById.get(id);
|
|
@@ -1717,6 +2050,10 @@ export async function compileManifest(
|
|
|
1717
2050
|
lessons.push({
|
|
1718
2051
|
id,
|
|
1719
2052
|
slug: id.replace(/\./g, '/'),
|
|
2053
|
+
editions: [
|
|
2054
|
+
...(isChapter(id) ? (['chapter'] as const) : []),
|
|
2055
|
+
...(lessonDeckFiles.some((deck) => deck.lessonId === id) ? (['deck'] as const) : []),
|
|
2056
|
+
],
|
|
1720
2057
|
// 0 = the lesson is not placed in any configured part; a curriculum
|
|
1721
2058
|
// diagnostic covers that separately, and both numbering paths then fall
|
|
1722
2059
|
// back to the legacy `##`-section scheme.
|
|
@@ -1836,6 +2173,127 @@ export async function compileManifest(
|
|
|
1836
2173
|
file: relative(REPOSITORY_ROOT, knowledgeChangelogPath).replace(/\\/g, '/'),
|
|
1837
2174
|
});
|
|
1838
2175
|
|
|
2176
|
+
// Each chapter's and part page's own version (rule C5): the committed
|
|
2177
|
+
// `content/versions.json`, brought to the pages' inputs as they are now —
|
|
2178
|
+
// the page's file, its checks and assessments, the shared notation entries
|
|
2179
|
+
// it uses, the sources it cites, and its numbers. With C5 off, nothing is
|
|
2180
|
+
// read or tracked and every page carries the course's version.
|
|
2181
|
+
const trackPageVersions = !rules.disabled.includes('C5');
|
|
2182
|
+
const previousVersions = trackPageVersions
|
|
2183
|
+
? readPageVersions(join(CONTENT_ROOT, 'versions.json'), PAGE_VERSIONS_FILE)
|
|
2184
|
+
: { record: undefined, diagnostics: [] };
|
|
2185
|
+
const digestOf = (file: string): string => hashes.content[file] ?? 'missing';
|
|
2186
|
+
const presentFiles = (files: readonly string[]) => files.filter((file) => hashes.content[file] !== undefined);
|
|
2187
|
+
const sourceFileById = new Map<string, string>();
|
|
2188
|
+
for (const record of contentRecords) {
|
|
2189
|
+
const file = record.file.replace(/\\/g, '/');
|
|
2190
|
+
if (file.startsWith('content/sources/') && typeof record.data.id === 'string') {
|
|
2191
|
+
sourceFileById.set(record.data.id, file);
|
|
2192
|
+
}
|
|
2193
|
+
}
|
|
2194
|
+
const sharedEntryInputs = (definitionIds: readonly string[]) =>
|
|
2195
|
+
definitionIds.flatMap((definitionId) => {
|
|
2196
|
+
const definition = definitionsById.get(definitionId);
|
|
2197
|
+
return definition?.kind === 'shared'
|
|
2198
|
+
? [{ label: `notation ${definition.source.file}`, digest: digestOf(definition.source.file) }]
|
|
2199
|
+
: [];
|
|
2200
|
+
});
|
|
2201
|
+
/** Each float a part's deck recalls, by ref, as its chapter numbers, captions and (an equation) writes it. */
|
|
2202
|
+
const recalledFloatFacts = (recalledFrom: Readonly<Record<string, string>>) =>
|
|
2203
|
+
Object.fromEntries(
|
|
2204
|
+
Object.entries(recalledFrom).map(([ref, chapter]) => {
|
|
2205
|
+
const [, prefix, key] = /^(eq|fig|tab|dia)-(.+)$/u.exec(ref) ?? [];
|
|
2206
|
+
const floats =
|
|
2207
|
+
prefix === 'eq'
|
|
2208
|
+
? equationsBySlug
|
|
2209
|
+
: prefix === 'fig'
|
|
2210
|
+
? figuresBySlug
|
|
2211
|
+
: prefix === 'tab'
|
|
2212
|
+
? tablesBySlug
|
|
2213
|
+
: diagramsBySlug;
|
|
2214
|
+
const float = floats.get(chapter)?.find((entry) => entry.key === key);
|
|
2215
|
+
const latex = prefix === 'eq' ? chapterFloatsOf(chapter.replace(/\//g, '.')).equations.get(key ?? '')?.latex : undefined;
|
|
2216
|
+
return [ref, { chapter, ...float, ...(latex === undefined ? {} : { latex }) }];
|
|
2217
|
+
}),
|
|
2218
|
+
);
|
|
2219
|
+
const trackedPages = [
|
|
2220
|
+
...lessons.map((lesson) => ({
|
|
2221
|
+
slug: lesson.slug,
|
|
2222
|
+
source: pageSourceDigest(
|
|
2223
|
+
[
|
|
2224
|
+
{ label: `page ${lesson.file}`, digest: digestOf(lesson.file) },
|
|
2225
|
+
...presentFiles([lesson.file.replace(/\.mdx?$/u, '.checks.yml')]).map((file) => ({
|
|
2226
|
+
label: `checks ${file}`,
|
|
2227
|
+
digest: digestOf(file),
|
|
2228
|
+
})),
|
|
2229
|
+
...presentFiles(lesson.assessments.map((id) => `content/assessments/${id}.json`)).map((file) => ({
|
|
2230
|
+
label: `assessment ${file}`,
|
|
2231
|
+
digest: digestOf(file),
|
|
2232
|
+
})),
|
|
2233
|
+
...sharedEntryInputs(lesson.notation.definitionIds),
|
|
2234
|
+
...lesson.sources.map((id) => {
|
|
2235
|
+
const file = sourceFileById.get(id);
|
|
2236
|
+
return { label: `source ${id}`, digest: file === undefined ? 'missing' : digestOf(file) };
|
|
2237
|
+
}),
|
|
2238
|
+
],
|
|
2239
|
+
{ part: lesson.part, chapter: lesson.chapter, numbering: lesson.numbering },
|
|
2240
|
+
),
|
|
2241
|
+
})),
|
|
2242
|
+
// A deck beside its chapter has a version of its own, by its route, and
|
|
2243
|
+
// so has a part's deck; a slides-first lesson's deck is the lesson, above.
|
|
2244
|
+
...deckModel.decks.flatMap((deck) => {
|
|
2245
|
+
if (deck.chapter === undefined && deck.part === undefined) return [];
|
|
2246
|
+
const page = deckPages.find((entry) => entry.deck.route === deck.slug);
|
|
2247
|
+
return [
|
|
2248
|
+
{
|
|
2249
|
+
slug: deck.slug,
|
|
2250
|
+
source: pageSourceDigest(
|
|
2251
|
+
[
|
|
2252
|
+
{ label: `page ${deck.file}`, digest: digestOf(deck.file) },
|
|
2253
|
+
...sharedEntryInputs(bundlesByLesson.get(deck.pageId)?.definitionIds ?? []),
|
|
2254
|
+
...(page === undefined ? [] : deriveSources(page.notation.body)).map((id) => {
|
|
2255
|
+
const file = sourceFileById.get(id);
|
|
2256
|
+
return { label: `source ${id}`, digest: file === undefined ? 'missing' : digestOf(file) };
|
|
2257
|
+
}),
|
|
2258
|
+
],
|
|
2259
|
+
// A part's deck shows each float it recalls as its chapter
|
|
2260
|
+
// numbers and captions it: those count as the page's too.
|
|
2261
|
+
deck.recalledFrom === undefined ? {} : { recalls: recalledFloatFacts(deck.recalledFrom) },
|
|
2262
|
+
),
|
|
2263
|
+
},
|
|
2264
|
+
];
|
|
2265
|
+
}),
|
|
2266
|
+
...manifestParts.flatMap((part) => {
|
|
2267
|
+
if (!part.overview) return [];
|
|
2268
|
+
const slug = `${part.dir}/index`;
|
|
2269
|
+
const page = pages.find((entry) => entry.slug === slug);
|
|
2270
|
+
return [
|
|
2271
|
+
{
|
|
2272
|
+
slug,
|
|
2273
|
+
source: pageSourceDigest([
|
|
2274
|
+
{ label: `page ${part.overview.file}`, digest: digestOf(part.overview.file) },
|
|
2275
|
+
...sharedEntryInputs(page?.notation.definitionIds ?? []),
|
|
2276
|
+
]),
|
|
2277
|
+
},
|
|
2278
|
+
];
|
|
2279
|
+
}),
|
|
2280
|
+
];
|
|
2281
|
+
const pageVersionsRecord = nextPageVersions({
|
|
2282
|
+
previous: previousVersions.record,
|
|
2283
|
+
version: knowledge.version,
|
|
2284
|
+
unreleased: knowledge.unreleased.length > 0,
|
|
2285
|
+
pages: trackedPages,
|
|
2286
|
+
});
|
|
2287
|
+
const pageVersions: ManifestPageVersions = {
|
|
2288
|
+
enabled: trackPageVersions,
|
|
2289
|
+
file: PAGE_VERSIONS_FILE,
|
|
2290
|
+
record: pageVersionsRecord,
|
|
2291
|
+
};
|
|
2292
|
+
const sinceOfPage = (slug: string): string | null => {
|
|
2293
|
+
const entry = pageVersionsRecord.pages[slug];
|
|
2294
|
+
return trackPageVersions && entry !== undefined ? sinceOf(entry) : knowledge.version;
|
|
2295
|
+
};
|
|
2296
|
+
|
|
1839
2297
|
const enginePkg = readEnginePackage();
|
|
1840
2298
|
const about: ManifestAbout = {
|
|
1841
2299
|
engine: {
|
|
@@ -1855,6 +2313,11 @@ export async function compileManifest(
|
|
|
1855
2313
|
? {}
|
|
1856
2314
|
: { date: knowledge.latest.date }),
|
|
1857
2315
|
notes: knowledge.latest?.notes ?? [],
|
|
2316
|
+
releases: knowledge.entries.map((entry) => ({
|
|
2317
|
+
version: entry.version,
|
|
2318
|
+
...(entry.date === undefined ? {} : { date: entry.date }),
|
|
2319
|
+
})),
|
|
2320
|
+
unreleased: knowledge.unreleased.length > 0,
|
|
1858
2321
|
contentTree: hashes.contentTree,
|
|
1859
2322
|
},
|
|
1860
2323
|
authors: courseConfig.about.authors.map((author) => ({
|
|
@@ -1867,6 +2330,37 @@ export async function compileManifest(
|
|
|
1867
2330
|
: { changelogHref: courseConfig.about.changelogHref }),
|
|
1868
2331
|
};
|
|
1869
2332
|
|
|
2333
|
+
const lessonsWithSince = lessons.map((lesson) => ({ ...lesson, since: sinceOfPage(lesson.slug) }));
|
|
2334
|
+
const decksWithSince = deckModel.decks.map((deck) => {
|
|
2335
|
+
const page = pageInputById.get(deck.pageId);
|
|
2336
|
+
return {
|
|
2337
|
+
...deck,
|
|
2338
|
+
since: sinceOfPage(deck.slug),
|
|
2339
|
+
notation: lessonNotationBundle(
|
|
2340
|
+
bundlesByLesson.get(deck.pageId),
|
|
2341
|
+
definitionsById,
|
|
2342
|
+
page === undefined ? [] : lessonNotationResolution(page, definitionsById, availableDefinitionIds),
|
|
2343
|
+
cardsByKey,
|
|
2344
|
+
// A deck shows its lessons' symbols: the derived cards (rule N23)
|
|
2345
|
+
// its chapters' bodies and checks use ship with it, as its bundle
|
|
2346
|
+
// holds the chapters' entries.
|
|
2347
|
+
[
|
|
2348
|
+
...(indexGate.derivedByLesson.get(deck.pageId) ?? []),
|
|
2349
|
+
...(page?.scopeLessonIds ?? []).flatMap((lessonId) => [
|
|
2350
|
+
...(indexGate.derivedByLesson.get(lessonId) ?? []),
|
|
2351
|
+
...(assessmentDerived.get(lessonId)?.values() ?? []),
|
|
2352
|
+
]),
|
|
2353
|
+
].filter((use, index, all) => all.findIndex((other) => other.key === use.key) === index),
|
|
2354
|
+
),
|
|
2355
|
+
};
|
|
2356
|
+
});
|
|
2357
|
+
// Rule D11: a lesson's own deck whose chapter changed after the deck did.
|
|
2358
|
+
// `since` is only known this late, so this runs after `mathIssues` (D1–D10's
|
|
2359
|
+
// frame codes) is already waived; pushed in after, it joins `diagnostics.math`
|
|
2360
|
+
// the same way, just outside the waiver pass — not a per-place finding to waive.
|
|
2361
|
+
const chapterSince = new Map(lessonsWithSince.map((lesson) => [lesson.slug, lesson.since] as const));
|
|
2362
|
+
mathIssues.push(...deckStaleDiagnostics(decksWithSince, chapterSince));
|
|
2363
|
+
|
|
1870
2364
|
return {
|
|
1871
2365
|
schemaVersion: MANIFEST_SCHEMA_VERSION,
|
|
1872
2366
|
lessonExports: normalizeLessonExports(courseConfig),
|
|
@@ -1874,6 +2368,12 @@ export async function compileManifest(
|
|
|
1874
2368
|
numbering: numberingSettings,
|
|
1875
2369
|
blockLabels,
|
|
1876
2370
|
cards: normalizeCards(courseConfig),
|
|
2371
|
+
sidebarMarks: normalizeSidebarMarks(courseConfig),
|
|
2372
|
+
sidebarEditions: normalizeSidebarEditions(courseConfig),
|
|
2373
|
+
readingMemory: normalizeReadingMemory(courseConfig),
|
|
2374
|
+
appearance: normalizeAppearance(courseConfig),
|
|
2375
|
+
deck: deckSettings,
|
|
2376
|
+
decks: decksWithSince,
|
|
1877
2377
|
about,
|
|
1878
2378
|
notation: {
|
|
1879
2379
|
definitions: registry.definitions,
|
|
@@ -1886,9 +2386,14 @@ export async function compileManifest(
|
|
|
1886
2386
|
sources,
|
|
1887
2387
|
tracks,
|
|
1888
2388
|
abbreviations,
|
|
1889
|
-
sidebar,
|
|
1890
|
-
parts: manifestParts,
|
|
1891
|
-
|
|
2389
|
+
sidebar: sidebarWithFrames,
|
|
2390
|
+
parts: manifestParts.map(({ overview, ...part }) =>
|
|
2391
|
+
overview
|
|
2392
|
+
? { ...part, overview: { ...overview, since: sinceOfPage(`${part.dir}/index`) } }
|
|
2393
|
+
: part,
|
|
2394
|
+
),
|
|
2395
|
+
lessons: lessonsWithSince,
|
|
2396
|
+
pageVersions,
|
|
1892
2397
|
pages,
|
|
1893
2398
|
prereqEdges,
|
|
1894
2399
|
equations: {
|
|
@@ -1930,7 +2435,7 @@ export async function compileManifest(
|
|
|
1930
2435
|
checks: dropDisabled(checkValidation.diagnostics, off),
|
|
1931
2436
|
examples: dropDisabled(exampleValidation.diagnostics, off),
|
|
1932
2437
|
folds: dropDisabled(foldValidation.diagnostics, off),
|
|
1933
|
-
knowledge: dropDisabled(knowledge.diagnostics, off),
|
|
2438
|
+
knowledge: dropDisabled([...knowledge.diagnostics, ...previousVersions.diagnostics], off),
|
|
1934
2439
|
rawFloats: dropDisabled(rawFloatDiagnostics, off),
|
|
1935
2440
|
config: rules.diagnostics,
|
|
1936
2441
|
waivers: waiverDiagnostics,
|