explico 0.12.0 → 0.13.1
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 +1137 -1
- package/README.md +91 -13
- package/dist/compiler/astro.d.ts +4 -3
- package/dist/compiler/astro.js +45 -15
- package/dist/compiler/cli.d.ts +26 -12
- package/dist/compiler/cli.js +96 -25
- package/dist/compiler/collections.d.ts +9 -1
- package/dist/compiler/collections.js +55 -16
- 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 +14 -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 +87 -6
- package/dist/compiler/manifest.js +174 -31
- package/dist/compiler/render-diagnostics.d.ts +65 -0
- package/dist/compiler/render-diagnostics.js +56 -0
- package/dist/compiler/resolution-report.d.ts +2 -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 +2 -2
- package/dist/reference/base-operators.mjs +2 -2
- 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/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-part-pages.d.ts +1 -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-math-scope.d.ts +1 -0
- package/dist/reference/lab-math-scope.js +13 -2
- 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 +34 -4
- package/dist/reference/page-notation.js +54 -2
- package/dist/reference/references.d.ts +11 -1
- package/dist/reference/references.js +6 -0
- package/dist/reference/registry.js +104 -10
- 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 +84 -20
- package/dist/reference/render-lab-math.d.ts +2 -0
- package/dist/reference/render-lab-math.js +17 -5
- 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 +191 -30
- package/dist/rules/in-effect.d.ts +1 -1
- package/dist/rules/rules.schema.json +79 -24
- 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 +32 -21
- package/docs/authoring/agents.md +38 -7
- package/docs/authoring/components.md +111 -6
- package/docs/authoring/migrating.md +707 -0
- package/docs/authoring/notation.md +281 -29
- package/docs/authoring/retired.md +1 -0
- package/docs/authoring/structure-rules.md +10 -41
- package/docs/authoring/structure.md +53 -2
- package/docs/authoring/visualizations.md +144 -18
- package/package.json +12 -7
- package/src/compiler/astro.ts +57 -17
- package/src/compiler/cli.ts +143 -35
- package/src/compiler/collections.ts +92 -19
- 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 +299 -28
- package/src/compiler/render-diagnostics.ts +111 -0
- package/src/components/HoverPanel.astro +204 -6
- package/src/components/PartOverview.astro +12 -9
- 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/figure/Figure.astro +11 -0
- package/src/components/figure/FloatCaption.astro +4 -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 +321 -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 +47 -9
- 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 +13 -21
- 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 +2 -2
- 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/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-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-math-scope.ts +15 -1
- package/src/reference/math-glyphs.mjs +851 -46
- package/src/reference/mixed-content.ts +11 -2
- package/src/reference/page-notation.ts +100 -3
- package/src/reference/references.ts +14 -0
- package/src/reference/registry.ts +154 -8
- 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 +84 -20
- package/src/reference/render-lab-math.ts +29 -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 +205 -29
- package/src/rules/settings.ts +10 -0
- package/src/session/PreferenceStore.ts +53 -0
- package/src/styles/global.css +291 -75
|
@@ -0,0 +1,391 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The content collections' schemas, one definition for both readers of
|
|
3
|
+
* `content/`: Astro's collections (`src/content-config.ts`, which wraps each
|
|
4
|
+
* in `defineCollection`) and the manifest walk, which checks every record
|
|
5
|
+
* against the same schema (`content-schema.ts`, `validateContentSchemas`) so
|
|
6
|
+
* `pnpm validate:content` rejects what `astro build` would.
|
|
7
|
+
*
|
|
8
|
+
* Plain zod only — no `astro:content` — so the manifest can import it outside
|
|
9
|
+
* Vite.
|
|
10
|
+
*/
|
|
11
|
+
import { z } from 'astro/zod';
|
|
12
|
+
import { isSubstantiveMeaning } from '../reference/prose';
|
|
13
|
+
import { glossKeySegment } from '../reference/gloss';
|
|
14
|
+
import { BASE_PLACEHOLDER } from '../reference/derived-notation.mjs';
|
|
15
|
+
|
|
16
|
+
export const contentId = z.string().regex(/^[a-z0-9]+(?:[.-][a-z0-9]+)*$/);
|
|
17
|
+
const id = contentId;
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Notation `meaning` / `summary`: plain prose (no `$`, `\`, backticks), and —
|
|
21
|
+
* the C1b safeguard — substantive, not a placeholder. A short or blocklisted
|
|
22
|
+
* phrase fails the build so a local symbol cannot get a glossary-style panel
|
|
23
|
+
* for a throwaway definition.
|
|
24
|
+
*/
|
|
25
|
+
export const notationProse = z
|
|
26
|
+
.string()
|
|
27
|
+
.min(1)
|
|
28
|
+
.refine((value) => !/[\\$`]/.test(value), {
|
|
29
|
+
message:
|
|
30
|
+
'Notation meaning/summary must be plain prose: no `$`, `\\` or backtick. Put a symbol or formula in its own field (`latex`, `formula`), and write an amount of money in words or with its currency code ("USD 5").',
|
|
31
|
+
})
|
|
32
|
+
.refine(isSubstantiveMeaning, {
|
|
33
|
+
message:
|
|
34
|
+
'Notation meaning must be substantive prose (at least four words, not a placeholder).',
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
const notationAlignment = z.discriminatedUnion('kind', [
|
|
38
|
+
z.object({
|
|
39
|
+
kind: z.literal('competency'),
|
|
40
|
+
introducedByCompetency: id,
|
|
41
|
+
introducedInLesson: id,
|
|
42
|
+
}),
|
|
43
|
+
z.object({
|
|
44
|
+
kind: z.literal('general'),
|
|
45
|
+
rationale: z.string().min(1),
|
|
46
|
+
}),
|
|
47
|
+
]);
|
|
48
|
+
const notationAlignmentDefault = notationAlignment.default({
|
|
49
|
+
kind: 'general',
|
|
50
|
+
rationale: 'General notation.',
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* A citation on a notation definition: a bare id (locator pending, see D6/g) or
|
|
55
|
+
* `{id, locator}` — a per-claim locator verified against the cited text.
|
|
56
|
+
*/
|
|
57
|
+
const notationSource = z.union([
|
|
58
|
+
id,
|
|
59
|
+
z.object({ id, locator: z.string().min(1) }),
|
|
60
|
+
]);
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* A **gloss** on a notation card: a symbol the card's own `formula` needs in
|
|
64
|
+
* order to be written down, carrying a name and nothing else (D15).
|
|
65
|
+
*
|
|
66
|
+
* A gloss is deliberately cheaper than a card. It has no description, no
|
|
67
|
+
* sources, no curriculum alignment, no review state, and no glossary card of
|
|
68
|
+
* its own; it is not a node in the curriculum graph and never creates a
|
|
69
|
+
* reference edge. Its key is derived, not authored — `<card key>.<slug(name)>`
|
|
70
|
+
* — so a gloss is scoped to the card that declares it and two cards may each
|
|
71
|
+
* gloss `T`. Because a formula spells LaTeX and never a key, **promoting** a
|
|
72
|
+
* gloss is exactly: delete this line, add `content/notation/<key>.md`.
|
|
73
|
+
*
|
|
74
|
+
* `units` is optional and stays a bare unit string: "T — time" is ambiguous in
|
|
75
|
+
* a corpus with several `T`s, "T — time, years from valuation" is not, and it
|
|
76
|
+
* is still one line. Prose belongs on a card.
|
|
77
|
+
*/
|
|
78
|
+
const notationGloss = z.object({
|
|
79
|
+
latex: z.string().min(1),
|
|
80
|
+
name: z
|
|
81
|
+
.string()
|
|
82
|
+
.min(1)
|
|
83
|
+
.refine((value) => glossKeySegment(value) !== undefined, {
|
|
84
|
+
message:
|
|
85
|
+
'A gloss name must contain letters or digits so a key can be derived from it.',
|
|
86
|
+
})
|
|
87
|
+
// The symbol goes in `latex`, the name names it. A name carrying LaTeX is
|
|
88
|
+
// the one unambiguous way to get this field wrong, so it blocks; a name
|
|
89
|
+
// that is merely long is a Tier-3 `gloss-name-shape` warning instead,
|
|
90
|
+
// because a complicated quantity may honestly need several words.
|
|
91
|
+
.refine((value) => !/[\\$`\n]/.test(value), {
|
|
92
|
+
message:
|
|
93
|
+
'A gloss name is a plain noun phrase; put the symbol in `latex`, not in the name.',
|
|
94
|
+
}),
|
|
95
|
+
units: z.string().min(1).optional(),
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
/** An index entry's latex: a subscript on an empty group, `{}_0`. */
|
|
99
|
+
export function isIndexEntryLatex(latex: string): boolean {
|
|
100
|
+
return /^\s*\{\s*\}\s*_/.test(latex);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* One notation entry shape (Phase C1b), shared by page-local `notation.local`
|
|
105
|
+
* and the standalone `notation` collection.
|
|
106
|
+
*
|
|
107
|
+
* Authored: `key`, `latex`, `meaning`, optional `formula`, optional `units`
|
|
108
|
+
* (or `dimensionless: true`), `seeAlso`, `sources` (`{id, locator}`),
|
|
109
|
+
* optional `alignment` (defaults to `general`), optional `label`; and for
|
|
110
|
+
* rule N23, `reads` on an index entry, `range` and `fixedIndex` on a letter.
|
|
111
|
+
*
|
|
112
|
+
* The display label defaults to the humanized `key` (see `reference/label.ts`);
|
|
113
|
+
* `label` is authored only where that reads wrong. The `notationProse`
|
|
114
|
+
* refinement rejects an under-specified `meaning`.
|
|
115
|
+
*/
|
|
116
|
+
const notationEntryObject = z.object({
|
|
117
|
+
key: id,
|
|
118
|
+
latex: z.string().min(1),
|
|
119
|
+
meaning: notationProse,
|
|
120
|
+
formula: z.string().min(1).optional(),
|
|
121
|
+
units: z.string().min(1).optional(),
|
|
122
|
+
dimensionless: z.literal(true).optional(),
|
|
123
|
+
seeAlso: z.array(id).default([]),
|
|
124
|
+
sources: z.array(notationSource).default([]),
|
|
125
|
+
alignment: notationAlignmentDefault,
|
|
126
|
+
label: z.string().min(1).optional(),
|
|
127
|
+
glosses: z.array(notationGloss).default([]),
|
|
128
|
+
/**
|
|
129
|
+
* An index entry's title for the symbol it indexes (rule N23):
|
|
130
|
+
* `"{base} evaluated at t = 0"`. Defaults to `"<label> of {base}"`.
|
|
131
|
+
*/
|
|
132
|
+
reads: z
|
|
133
|
+
.string()
|
|
134
|
+
.min(1)
|
|
135
|
+
.refine((value) => value.includes(BASE_PLACEHOLDER), {
|
|
136
|
+
message: `reads names the indexed symbol with ${BASE_PLACEHOLDER}, e.g. "${BASE_PLACEHOLDER} evaluated at t = 0".`,
|
|
137
|
+
})
|
|
138
|
+
.refine((value) => !/[\\$`]/.test(value), {
|
|
139
|
+
message: 'reads is plain prose: no `$`, `\\` or backtick.',
|
|
140
|
+
})
|
|
141
|
+
.optional(),
|
|
142
|
+
/** The set a running index letter runs over (rule N23): `1, \dots, n`. */
|
|
143
|
+
range: z.string().min(1).optional(),
|
|
144
|
+
/** As an index, this letter (or this entry's own index) is a fixed label. */
|
|
145
|
+
fixedIndex: z.literal(true).optional(),
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Rule N23's fields belong to one kind of entry each: `reads` to an index
|
|
150
|
+
* entry (`{}_0`), and `range`, `fixedIndex` and units to anything else — a
|
|
151
|
+
* subscript has no unit, and a derived card takes the indexed symbol's.
|
|
152
|
+
*/
|
|
153
|
+
function refineIndexFields(
|
|
154
|
+
entry: z.infer<typeof notationEntryObject>,
|
|
155
|
+
context: z.RefinementCtx,
|
|
156
|
+
): void {
|
|
157
|
+
const index = isIndexEntryLatex(entry.latex);
|
|
158
|
+
if (entry.reads !== undefined && !index) {
|
|
159
|
+
context.addIssue({
|
|
160
|
+
code: z.ZodIssueCode.custom,
|
|
161
|
+
path: ['reads'],
|
|
162
|
+
message:
|
|
163
|
+
'reads belongs on an index entry, whose latex is a subscript on an empty group ({}_0).',
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
for (const field of ['range', 'fixedIndex', 'units', 'dimensionless'] as const) {
|
|
167
|
+
if (index && entry[field] !== undefined) {
|
|
168
|
+
context.addIssue({
|
|
169
|
+
code: z.ZodIssueCode.custom,
|
|
170
|
+
path: [field],
|
|
171
|
+
message: `An index entry ({}_…) takes no ${field}: a derived card takes its units from the symbol it indexes, and the index it names is fixed already.`,
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
export const notationEntryShape = notationEntryObject.superRefine(refineIndexFields);
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* One `notation.suspend` item (rule N22): a decoration (`'`, `\hat`, …) that
|
|
181
|
+
* is not an operator on this page. The loader (`collections.ts`) checks the
|
|
182
|
+
* name against the decorations the resolver knows.
|
|
183
|
+
*/
|
|
184
|
+
const notationSuspension = z.object({
|
|
185
|
+
operator: z.string().min(1),
|
|
186
|
+
reason: z.string().min(1).optional(),
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
const lessonNotation = z
|
|
190
|
+
.object({
|
|
191
|
+
// `notation.uses` is retired (D3): a lesson pulls a shared key into scope
|
|
192
|
+
// by referencing it in prose (`[[key]]`) or math (`\explain`).
|
|
193
|
+
local: z.array(notationEntryShape).default([]),
|
|
194
|
+
suspend: z.array(notationSuspension).default([]),
|
|
195
|
+
})
|
|
196
|
+
.default({ local: [], suspend: [] });
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* How long this chapter takes to study: `40`, or a `[low, high]` range in
|
|
200
|
+
* minutes. It is a reader-facing estimate the author makes, aggregated by
|
|
201
|
+
* `<PartOverview>` and `<CourseContents>` into a part and course total — not
|
|
202
|
+
* the provenance field `estimatedMinutes` used to be, and nothing derives it.
|
|
203
|
+
*
|
|
204
|
+
* A part only reports a total when every one of its chapters states one, so a
|
|
205
|
+
* half-annotated course shows the chapters it knows about and no misleading
|
|
206
|
+
* sum.
|
|
207
|
+
*/
|
|
208
|
+
const studyMinutes = z
|
|
209
|
+
.union([
|
|
210
|
+
z.number().int().positive(),
|
|
211
|
+
z.tuple([z.number().int().positive(), z.number().int().positive()]),
|
|
212
|
+
])
|
|
213
|
+
.refine((value) => !Array.isArray(value) || value[0] <= value[1], {
|
|
214
|
+
message:
|
|
215
|
+
'studyMinutes as a range is [low, high]; the low end cannot exceed the high end.',
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* Lesson frontmatter — reduced shape (Phase C1 schema, C2 codemod applied):
|
|
220
|
+
* the fields explico adds to Starlight's own (`title`, `description`, …).
|
|
221
|
+
*
|
|
222
|
+
* Authored: `teaches` (ordered — the curriculum contract, never derived),
|
|
223
|
+
* `assumptions`, `studyMinutes` (optional reader-facing estimate),
|
|
224
|
+
* `notation.local` (page-local symbols only).
|
|
225
|
+
* Derived (in `compiler/collections.ts` + `compiler/lesson-derivation.ts`,
|
|
226
|
+
* never authored): `lessonId` (doc slug), `requires` (direct
|
|
227
|
+
* competency-DAG prerequisites of `teaches`, minus `teaches`),
|
|
228
|
+
* `sources` (`[@…]` occurrences), `assessments` (colocated
|
|
229
|
+
* `<lesson>.checks.yml`), `sidebar` order (track order).
|
|
230
|
+
* Dropped: `aiAssisted`, `lastReviewed`, `riskTier` (git history carries
|
|
231
|
+
* provenance); `editorialStatus`, the draft / in-review / reviewed
|
|
232
|
+
* flag, because lessons are reviewed continuously and never earn a
|
|
233
|
+
* one-time stamp (a file that still sets it fails the build, see
|
|
234
|
+
* `reference/gate-retired-keys.ts`); `estimatedMinutes`, whose
|
|
235
|
+
* reader-facing half returned as the optional `studyMinutes` once
|
|
236
|
+
* part pages had somewhere to total it; `notation.uses` (D3 — a
|
|
237
|
+
* shared symbol is imported by using it).
|
|
238
|
+
*/
|
|
239
|
+
export const lessonFrontmatterSchema = z.object({
|
|
240
|
+
teaches: z.array(id).default([]),
|
|
241
|
+
assumptions: z.array(z.string().min(1)).default([]),
|
|
242
|
+
studyMinutes: studyMinutes.optional(),
|
|
243
|
+
notation: lessonNotation,
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
export const competencySchema = z.object({
|
|
247
|
+
id,
|
|
248
|
+
title: z.string().min(1),
|
|
249
|
+
domain: id,
|
|
250
|
+
facet: z.enum([
|
|
251
|
+
'knowledge',
|
|
252
|
+
'calculation',
|
|
253
|
+
'interpretation',
|
|
254
|
+
'convention',
|
|
255
|
+
'risk',
|
|
256
|
+
]),
|
|
257
|
+
outcome: z.string().min(1),
|
|
258
|
+
prerequisites: z.array(id).default([]),
|
|
259
|
+
evidence: z.object({
|
|
260
|
+
minimumIndependentItems: z.number().int().min(1),
|
|
261
|
+
requiresTransfer: z.boolean(),
|
|
262
|
+
requiresUnassistedPass: z.boolean(),
|
|
263
|
+
}),
|
|
264
|
+
misconceptions: z.array(z.string().min(1)).default([]),
|
|
265
|
+
});
|
|
266
|
+
|
|
267
|
+
const assessmentItemBase = z.object({
|
|
268
|
+
id,
|
|
269
|
+
competencyId: id,
|
|
270
|
+
evidenceKind: z.enum(['direct', 'transfer']),
|
|
271
|
+
prompt: z.string().min(1),
|
|
272
|
+
explanation: z.string().min(1),
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
/**
|
|
276
|
+
* `AssessmentSet.astro` renders the numbered heading itself — `<h2>Knowledge
|
|
277
|
+
* check {number} — {title}</h2>` — so `title` must be just the topic. A title
|
|
278
|
+
* that repeats the "Knowledge check" label duplicates it on every page that
|
|
279
|
+
* uses `AssessmentSet` (the standalone `AssessmentRunner` heading fallback
|
|
280
|
+
* reads fine either way, so this only bites the common path).
|
|
281
|
+
*/
|
|
282
|
+
const assessmentTitle = z
|
|
283
|
+
.string()
|
|
284
|
+
.min(1)
|
|
285
|
+
.refine((value) => !/^knowledge check\b/i.test(value), {
|
|
286
|
+
message:
|
|
287
|
+
'Assessment title must not start with "Knowledge check" — AssessmentSet already renders "Knowledge check {number} — {title}", so a title with the same prefix duplicates it. Use just the topic, e.g. "AIC and KL risk".',
|
|
288
|
+
});
|
|
289
|
+
|
|
290
|
+
export const assessmentSchema = z.object({
|
|
291
|
+
id,
|
|
292
|
+
title: assessmentTitle,
|
|
293
|
+
items: z
|
|
294
|
+
.array(
|
|
295
|
+
z.discriminatedUnion('type', [
|
|
296
|
+
assessmentItemBase.extend({
|
|
297
|
+
type: z.literal('numeric'),
|
|
298
|
+
answer: z.object({
|
|
299
|
+
value: z.number(),
|
|
300
|
+
tolerance: z.number().positive(),
|
|
301
|
+
}),
|
|
302
|
+
}),
|
|
303
|
+
assessmentItemBase.extend({
|
|
304
|
+
type: z.literal('single-choice'),
|
|
305
|
+
options: z
|
|
306
|
+
.array(
|
|
307
|
+
z.object({
|
|
308
|
+
id,
|
|
309
|
+
label: z.string().min(1),
|
|
310
|
+
}),
|
|
311
|
+
)
|
|
312
|
+
.min(2),
|
|
313
|
+
correctOptionId: id,
|
|
314
|
+
}),
|
|
315
|
+
]),
|
|
316
|
+
)
|
|
317
|
+
.min(1),
|
|
318
|
+
});
|
|
319
|
+
|
|
320
|
+
export const trackSchema = z.object({
|
|
321
|
+
id,
|
|
322
|
+
title: z.string().min(1),
|
|
323
|
+
description: z.string().min(1),
|
|
324
|
+
audience: z.string().min(1),
|
|
325
|
+
entryAssumptions: z.array(z.string().min(1)),
|
|
326
|
+
lessons: z.array(id).min(1),
|
|
327
|
+
});
|
|
328
|
+
|
|
329
|
+
export const sourceSchema = z.object({
|
|
330
|
+
id,
|
|
331
|
+
type: z.enum([
|
|
332
|
+
'primary',
|
|
333
|
+
'official-guidance',
|
|
334
|
+
'paper',
|
|
335
|
+
'book',
|
|
336
|
+
'secondary',
|
|
337
|
+
]),
|
|
338
|
+
title: z.string().min(1),
|
|
339
|
+
authors: z.array(z.string().min(1)).optional(),
|
|
340
|
+
organization: z.string().min(1).optional(),
|
|
341
|
+
edition: z.string().optional(),
|
|
342
|
+
publisher: z.string().optional(),
|
|
343
|
+
year: z.number().int().optional(),
|
|
344
|
+
isbn: z.string().optional(),
|
|
345
|
+
url: z.url().optional(),
|
|
346
|
+
locator: z.string().optional(),
|
|
347
|
+
accessed: z.coerce.date().optional(),
|
|
348
|
+
licenseNotes: z.string().optional(),
|
|
349
|
+
});
|
|
350
|
+
|
|
351
|
+
/**
|
|
352
|
+
* Standalone notation collection — the one `notationEntry` shape plus the
|
|
353
|
+
* shared-only extras: `domain`, `aliases`, and a Markdown body.
|
|
354
|
+
*/
|
|
355
|
+
export const notationSchema = notationEntryObject
|
|
356
|
+
.extend({
|
|
357
|
+
domain: id,
|
|
358
|
+
aliases: z.array(z.string().min(1)).default([]),
|
|
359
|
+
aiAssisted: z.boolean().default(false),
|
|
360
|
+
})
|
|
361
|
+
.superRefine(refineIndexFields);
|
|
362
|
+
|
|
363
|
+
/**
|
|
364
|
+
* Abbreviations table (rule N14) — deliberately a lighter tier than a
|
|
365
|
+
* `notation` card: just a name resolution, no `formula`, `units`, or
|
|
366
|
+
* curriculum `alignment`. `abbr` is the canonical written form (`OLS`);
|
|
367
|
+
* `aliases` are other written forms of the same abbreviation that should
|
|
368
|
+
* resolve the same way (`w.r.t.` might alias `wrt`).
|
|
369
|
+
*/
|
|
370
|
+
export const abbreviationSchema = z.object({
|
|
371
|
+
key: id,
|
|
372
|
+
abbr: z.string().min(1),
|
|
373
|
+
expansion: notationProse,
|
|
374
|
+
aliases: z.array(z.string().min(1)).default([]),
|
|
375
|
+
domain: id.optional(),
|
|
376
|
+
});
|
|
377
|
+
|
|
378
|
+
/**
|
|
379
|
+
* Each collection's directory under `content/` and the schema a record there
|
|
380
|
+
* must satisfy. `docs` checks only the fields explico adds; Starlight's own
|
|
381
|
+
* (`title`, `description`, …) are Starlight's schema's to judge.
|
|
382
|
+
*/
|
|
383
|
+
export const CONTENT_SCHEMAS = {
|
|
384
|
+
docs: lessonFrontmatterSchema,
|
|
385
|
+
competencies: competencySchema,
|
|
386
|
+
assessments: assessmentSchema,
|
|
387
|
+
tracks: trackSchema,
|
|
388
|
+
sources: sourceSchema,
|
|
389
|
+
notation: notationSchema,
|
|
390
|
+
abbreviations: abbreviationSchema,
|
|
391
|
+
} as const;
|
|
@@ -36,13 +36,6 @@ export interface CoursePart {
|
|
|
36
36
|
*/
|
|
37
37
|
export type CourseSection = CoursePart;
|
|
38
38
|
|
|
39
|
-
export interface CourseConventions {
|
|
40
|
-
/** Notation key a lesson's stated sign convention must resolve to. */
|
|
41
|
-
readonly signConventionKey: string;
|
|
42
|
-
/** Competency id that also counts as declaring the cash-flow perspective. */
|
|
43
|
-
readonly cashFlowPerspectiveCompetency: string;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
39
|
/**
|
|
47
40
|
* A course's edits to the shipped named-operator baseline (`BASE_OPERATORS`),
|
|
48
41
|
* consumed by the `operator-explained` consistency check (rule N8). An operator
|
|
@@ -360,6 +353,39 @@ export function normalizeBlockLabels(config: CourseConfig): BlockLabels {
|
|
|
360
353
|
return labels;
|
|
361
354
|
}
|
|
362
355
|
|
|
356
|
+
/**
|
|
357
|
+
* How a course's explanation cards start for a reader who has not chosen
|
|
358
|
+
* yet (`cards` in the course settings). Every field is optional;
|
|
359
|
+
* {@link normalizeCards} fills in the defaults.
|
|
360
|
+
*/
|
|
361
|
+
export interface CourseCards {
|
|
362
|
+
/**
|
|
363
|
+
* Whether "Carry cards" starts on (rule K1, `card-carry.ts`): a kept card
|
|
364
|
+
* stays on screen as the page scrolls and comes along to every page of the
|
|
365
|
+
* course. A reader's own choice always wins. Default `false`.
|
|
366
|
+
*/
|
|
367
|
+
readonly carry?: boolean;
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
/** {@link CourseCards} with every default applied. */
|
|
371
|
+
export interface NormalizedCards {
|
|
372
|
+
readonly carry: boolean;
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
/** The engine's defaults: a reader turns carrying on (D8). */
|
|
376
|
+
export const CARDS_DEFAULTS: NormalizedCards = { carry: false };
|
|
377
|
+
|
|
378
|
+
/** Validate `CourseConfig.cards` and apply its defaults. Throws on a bad value. */
|
|
379
|
+
export function normalizeCards(config: CourseConfig): NormalizedCards {
|
|
380
|
+
const carry: unknown = config.cards?.carry ?? CARDS_DEFAULTS.carry;
|
|
381
|
+
if (typeof carry !== 'boolean') {
|
|
382
|
+
throw new Error(
|
|
383
|
+
`CourseConfig cards.carry must be true or false; received ${JSON.stringify(carry)}.`,
|
|
384
|
+
);
|
|
385
|
+
}
|
|
386
|
+
return { carry };
|
|
387
|
+
}
|
|
388
|
+
|
|
363
389
|
/**
|
|
364
390
|
* Rule S2's word ranges (`rules.S2.bridgeMinWords` … in the course settings),
|
|
365
391
|
* in the shape `gate-part-pages.ts` takes. Built by `partPageLimits` in
|
|
@@ -370,15 +396,33 @@ export interface NormalizedPartPages {
|
|
|
370
396
|
readonly scope: { readonly minWords: number; readonly maxWords: number };
|
|
371
397
|
}
|
|
372
398
|
|
|
373
|
-
/**
|
|
399
|
+
/**
|
|
400
|
+
* Optional, build-time downloads and print actions: each chapter, each part and
|
|
401
|
+
* the course download; every page prints.
|
|
402
|
+
*/
|
|
374
403
|
export interface LessonExportsConfig {
|
|
375
|
-
/** Emit one self-contained, offline-capable HTML file per
|
|
404
|
+
/** Emit one self-contained, offline-capable HTML file per page. */
|
|
376
405
|
readonly standaloneHtml?: boolean;
|
|
377
|
-
/** Offer the browser's native print dialog with explico's
|
|
406
|
+
/** Offer the browser's native print dialog with explico's print layout. */
|
|
378
407
|
readonly print?: boolean;
|
|
408
|
+
/**
|
|
409
|
+
* Emit each chapter's LaTeX source — a zipped folder with explico's class,
|
|
410
|
+
* the chapter and its figures — and the PDF compiled from that folder.
|
|
411
|
+
*/
|
|
412
|
+
readonly latex?: boolean;
|
|
413
|
+
/**
|
|
414
|
+
* Paper size of the LaTeX documents: `letter` (default, LaTeX's own) or
|
|
415
|
+
* `a4`. A plain `string` because the settings arrive as JSON.
|
|
416
|
+
*/
|
|
417
|
+
readonly latexPaper?: string;
|
|
418
|
+
/**
|
|
419
|
+
* Emit each chapter, each part and the course as Markdown — GitHub's
|
|
420
|
+
* flavour with `$…$` math, its figures as PNG — zipped with its assets.
|
|
421
|
+
*/
|
|
422
|
+
readonly markdown?: boolean;
|
|
379
423
|
/**
|
|
380
424
|
* Published course root used for links that intentionally leave the exported
|
|
381
|
-
*
|
|
425
|
+
* page. Required at export time when such a link is present.
|
|
382
426
|
*/
|
|
383
427
|
readonly canonicalBaseUrl?: string;
|
|
384
428
|
}
|
|
@@ -387,6 +431,9 @@ export interface LessonExportsConfig {
|
|
|
387
431
|
export interface NormalizedLessonExports {
|
|
388
432
|
readonly standaloneHtml: boolean;
|
|
389
433
|
readonly print: boolean;
|
|
434
|
+
readonly latex: boolean;
|
|
435
|
+
readonly latexPaper: 'letter' | 'a4';
|
|
436
|
+
readonly markdown: boolean;
|
|
390
437
|
readonly canonicalBaseUrl?: string;
|
|
391
438
|
}
|
|
392
439
|
|
|
@@ -405,7 +452,12 @@ export interface CourseConfig {
|
|
|
405
452
|
readonly sections?: readonly CoursePart[];
|
|
406
453
|
/** Namespaces used by competency / notation `domain` fields. */
|
|
407
454
|
readonly domains: readonly string[];
|
|
408
|
-
|
|
455
|
+
/**
|
|
456
|
+
* @deprecated No longer read: rule S5, the only reader, was retired. A
|
|
457
|
+
* course that still sets it gets a `config-deprecated-key` warning; delete
|
|
458
|
+
* the block. Removed from the type in the next MINOR release.
|
|
459
|
+
*/
|
|
460
|
+
readonly conventions?: unknown;
|
|
409
461
|
/** Reader-facing identity + knowledge version (the `[?]` About panel). */
|
|
410
462
|
readonly about: CourseAbout;
|
|
411
463
|
/**
|
|
@@ -428,12 +480,14 @@ export interface CourseConfig {
|
|
|
428
480
|
readonly romanLabels?: CourseRomanLabels;
|
|
429
481
|
/** @deprecated Moved to `rules.N14` (same shape). Honoured for now; removed in a later MINOR. */
|
|
430
482
|
readonly abbreviations?: CourseAbbreviations;
|
|
431
|
-
/** Per-
|
|
483
|
+
/** Per-page export actions and downloadable artifacts. Disabled when omitted. */
|
|
432
484
|
readonly lessonExports?: LessonExportsConfig;
|
|
433
485
|
/** Every collapse default: headings, checks, memos, floats, tables. */
|
|
434
486
|
readonly disclosure?: CourseDisclosure;
|
|
435
487
|
/** Outline and float numbering. The defaults apply when omitted. */
|
|
436
488
|
readonly numbering?: CourseNumbering;
|
|
489
|
+
/** How explanation cards start for a reader: "Carry cards" on or off. */
|
|
490
|
+
readonly cards?: CourseCards;
|
|
437
491
|
/**
|
|
438
492
|
* How a check's and an example set's heading names its kind: `icon-word`
|
|
439
493
|
* (default) or `icon` ({@link BlockLabels}). A plain `string` because the
|
|
@@ -449,8 +503,18 @@ export function normalizeLessonExports(
|
|
|
449
503
|
): NormalizedLessonExports {
|
|
450
504
|
const standaloneHtml = config.lessonExports?.standaloneHtml === true;
|
|
451
505
|
const print = config.lessonExports?.print === true;
|
|
506
|
+
const latex = config.lessonExports?.latex === true;
|
|
507
|
+
const rawPaper = config.lessonExports?.latexPaper ?? 'letter';
|
|
508
|
+
if (rawPaper !== 'letter' && rawPaper !== 'a4') {
|
|
509
|
+
throw new Error(
|
|
510
|
+
`CourseConfig lessonExports.latexPaper must be "letter" or "a4"; ` +
|
|
511
|
+
`received ${JSON.stringify(rawPaper)}.`,
|
|
512
|
+
);
|
|
513
|
+
}
|
|
514
|
+
const latexPaper: 'letter' | 'a4' = rawPaper;
|
|
515
|
+
const markdown = config.lessonExports?.markdown === true;
|
|
452
516
|
const rawBase = config.lessonExports?.canonicalBaseUrl;
|
|
453
|
-
if (rawBase === undefined) return { standaloneHtml, print };
|
|
517
|
+
if (rawBase === undefined) return { standaloneHtml, print, latex, latexPaper, markdown };
|
|
454
518
|
|
|
455
519
|
let parsed: URL;
|
|
456
520
|
try {
|
|
@@ -473,7 +537,14 @@ export function normalizeLessonExports(
|
|
|
473
537
|
);
|
|
474
538
|
}
|
|
475
539
|
if (!parsed.pathname.endsWith('/')) parsed.pathname += '/';
|
|
476
|
-
return {
|
|
540
|
+
return {
|
|
541
|
+
standaloneHtml,
|
|
542
|
+
print,
|
|
543
|
+
latex,
|
|
544
|
+
latexPaper,
|
|
545
|
+
markdown,
|
|
546
|
+
canonicalBaseUrl: parsed.toString(),
|
|
547
|
+
};
|
|
477
548
|
}
|
|
478
549
|
|
|
479
550
|
/**
|