explico 0.3.0 → 0.5.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 +344 -1
- package/README.md +117 -3
- package/dist/compiler/astro.d.ts +38 -0
- package/dist/compiler/astro.js +258 -0
- package/dist/compiler/cli.d.ts +13 -0
- package/dist/compiler/cli.js +32 -12
- package/dist/compiler/collections.d.ts +3 -2
- package/dist/compiler/collections.js +28 -1
- package/dist/compiler/course-config.d.ts +86 -4
- package/dist/compiler/course-config.js +39 -0
- package/dist/compiler/exports/standalone-html.d.ts +34 -0
- package/dist/compiler/exports/standalone-html.js +803 -0
- package/dist/compiler/lesson-derivation.d.ts +1 -1
- package/dist/compiler/lesson-derivation.js +2 -2
- package/dist/compiler/manifest.d.ts +112 -7
- package/dist/compiler/manifest.js +205 -40
- package/dist/compiler/resolution-report.d.ts +4 -0
- package/dist/reference/base-abbreviations.d.mts +30 -0
- package/dist/reference/base-abbreviations.mjs +60 -0
- package/dist/reference/base-operators.d.mts +15 -0
- package/dist/reference/base-operators.mjs +40 -0
- package/dist/reference/base-roman-labels.d.mts +35 -0
- package/dist/reference/base-roman-labels.mjs +43 -0
- package/dist/reference/consistency.d.ts +8 -2
- package/dist/reference/consistency.js +50 -3
- package/dist/reference/diagrams-validate.d.ts +21 -0
- package/dist/reference/diagrams-validate.js +13 -0
- package/dist/reference/equations-validate.d.ts +8 -3
- package/dist/reference/equations-validate.js +1 -1
- package/dist/reference/equations.d.mts +66 -35
- package/dist/reference/equations.mjs +142 -40
- package/dist/reference/gate-abbreviations.d.ts +17 -0
- package/dist/reference/gate-abbreviations.js +64 -0
- package/dist/reference/gate-math.d.ts +28 -5
- package/dist/reference/gate-math.js +5 -2
- package/dist/reference/gate-prose-notation.d.ts +60 -0
- package/dist/reference/gate-prose-notation.js +125 -0
- package/dist/reference/keyed-float-validate.d.ts +14 -8
- package/dist/reference/keyed-float-validate.js +1 -1
- package/dist/reference/keyed-jsx-floats.d.mts +24 -21
- package/dist/reference/keyed-jsx-floats.mjs +90 -41
- package/dist/reference/knowledge-checks-validate.d.ts +35 -0
- package/dist/reference/knowledge-checks-validate.js +78 -0
- package/dist/reference/knowledge-checks.d.mts +25 -0
- package/dist/reference/knowledge-checks.mjs +45 -0
- package/dist/reference/lab-math-scope.d.ts +9 -1
- package/dist/reference/lab-math-scope.js +32 -4
- package/dist/reference/math-glyphs.d.mts +30 -0
- package/dist/reference/math-glyphs.mjs +367 -0
- package/dist/reference/mdx-attributes.d.mts +13 -0
- package/dist/reference/mdx-attributes.mjs +38 -0
- package/dist/reference/mixed-content.d.ts +17 -0
- package/dist/reference/mixed-content.js +29 -0
- package/dist/reference/page-notation.d.ts +52 -0
- package/dist/reference/page-notation.js +171 -0
- package/dist/reference/raw-floats-validate.d.ts +18 -0
- package/dist/reference/raw-floats-validate.js +53 -0
- package/dist/reference/raw-floats.d.mts +18 -0
- package/dist/reference/raw-floats.mjs +129 -0
- package/dist/reference/rehype-heading-anchors.d.mts +1 -1
- package/dist/reference/rehype-heading-anchors.mjs +107 -1
- package/dist/reference/rehype-table-header-labels.d.mts +26 -0
- package/dist/reference/rehype-table-header-labels.mjs +152 -0
- package/dist/reference/remark-abbreviations.d.mts +7 -0
- package/dist/reference/remark-abbreviations.mjs +174 -0
- package/dist/reference/remark-memo.d.mts +16 -0
- package/dist/reference/remark-memo.mjs +339 -0
- package/dist/reference/remark-notation.d.mts +102 -5
- package/dist/reference/remark-notation.mjs +506 -50
- package/dist/reference/remark-short-note.d.mts +4 -0
- package/dist/reference/remark-short-note.mjs +237 -0
- package/dist/reference/types.d.ts +16 -1
- package/dist/reference/units-vocab.js +4 -0
- package/docs/VERSIONING.md +33 -4
- package/docs/authoring/README.md +12 -0
- package/docs/authoring/agents.md +17 -2
- package/docs/authoring/components.md +135 -0
- package/docs/authoring/migrating.md +154 -7
- package/docs/authoring/notation.md +568 -8
- package/docs/authoring/structure.md +251 -0
- package/docs/authoring/visualizations.md +36 -3
- package/package.json +5 -2
- package/src/compiler/astro.ts +332 -0
- package/src/compiler/cli.ts +33 -10
- package/src/compiler/collections.ts +45 -4
- package/src/compiler/course-config.ts +131 -3
- package/src/compiler/exports/standalone-html.ts +963 -0
- package/src/compiler/lesson-derivation.ts +2 -2
- package/src/compiler/manifest.ts +367 -49
- package/src/components/HoverPanel.astro +45 -4
- package/src/components/abbreviation/AbbreviationGlossary.astro +70 -0
- package/src/components/abbreviation/AbbreviationLayer.astro +127 -0
- package/src/components/assessments/AssessmentRunner.tsx +52 -17
- package/src/components/assessments/AssessmentSet.astro +98 -14
- package/src/components/assessments/RenderedContent.tsx +55 -0
- package/src/components/assessments/assessment-randomization.ts +16 -0
- package/src/components/diagram/Diagram.astro +444 -83
- package/src/components/disclosure/hover-preview.ts +202 -0
- package/src/components/disclosure/persisted-disclosure.ts +66 -0
- package/src/components/examples/CompactExamples.astro +2 -0
- package/src/components/figure/Figure.astro +279 -10
- package/src/components/memo/Memo.astro +453 -0
- package/src/components/memo/ShortNote.astro +223 -0
- package/src/components/notation/NotationFormula.astro +18 -1
- package/src/components/notation/NotationLayer.astro +116 -8
- package/src/components/notation/render-notation-math.ts +39 -2
- package/src/components/notation/safe-math-to-react.ts +50 -0
- package/src/components/reference/FloatRefLayer.astro +233 -15
- package/src/components/starlight/CollapsibleSections.astro +461 -0
- package/src/components/starlight/DiagramEnhancer.astro +385 -0
- package/src/components/starlight/HeadingAnchorEnhancer.astro +5 -17
- package/src/components/starlight/KeyedFloatEnhancer.astro +81 -3
- package/src/components/starlight/LayoutHeader.astro +50 -0
- package/src/components/starlight/LayoutSidebar.astro +62 -1
- package/src/components/starlight/LayoutSocialIcons.astro +41 -2
- package/src/components/starlight/LessonExportActions.astro +436 -0
- package/src/components/starlight/LessonFooter.astro +38 -2
- package/src/components/starlight/section-parts.ts +31 -0
- package/src/components/table/Table.astro +296 -9
- package/src/components/table/TableOverflowExpander.astro +489 -0
- package/src/content-config.ts +20 -0
- package/src/reference/base-abbreviations.mjs +60 -0
- package/src/reference/base-operators.mjs +40 -0
- package/src/reference/base-roman-labels.mjs +43 -0
- package/src/reference/consistency.ts +73 -2
- package/src/reference/diagrams-validate.ts +40 -0
- package/src/reference/equations-validate.ts +6 -4
- package/src/reference/equations.mjs +142 -40
- package/src/reference/gate-abbreviations.ts +128 -0
- package/src/reference/gate-math.ts +27 -7
- package/src/reference/gate-prose-notation.ts +224 -0
- package/src/reference/keyed-float-validate.ts +12 -9
- package/src/reference/keyed-jsx-floats.mjs +90 -41
- package/src/reference/knowledge-checks-validate.ts +130 -0
- package/src/reference/knowledge-checks.mjs +45 -0
- package/src/reference/lab-math-scope.ts +45 -3
- package/src/reference/math-glyphs.mjs +367 -0
- package/src/reference/mdx-attributes.mjs +38 -0
- package/src/reference/mixed-content.ts +69 -0
- package/src/reference/page-notation.ts +252 -0
- package/src/reference/raw-floats-validate.ts +103 -0
- package/src/reference/raw-floats.mjs +129 -0
- package/src/reference/rehype-heading-anchors.mjs +107 -1
- package/src/reference/rehype-table-header-labels.mjs +152 -0
- package/src/reference/remark-abbreviations.mjs +174 -0
- package/src/reference/remark-memo.mjs +339 -0
- package/src/reference/remark-notation.mjs +506 -50
- package/src/reference/remark-short-note.mjs +237 -0
- package/src/reference/types.ts +16 -0
- package/src/reference/units-vocab.ts +4 -0
- package/src/styles/global.css +1125 -50
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The base abbreviation baseline: acronyms and dotted abbreviations every
|
|
3
|
+
* reader is assumed to know, so a lesson never has to introduce them with an
|
|
4
|
+
* abbreviations-table entry. The `\operatorname{…}` / `\mathrm{…}` twins are
|
|
5
|
+
* `base-operators.mjs` (rule N8) / `base-roman-labels.mjs` (rule N10); this is
|
|
6
|
+
* the plain-prose equivalent for rule N14.
|
|
7
|
+
*
|
|
8
|
+
* Kept intentionally small, same rationale as `BASE_ROMAN_LABELS`: a
|
|
9
|
+
* course-specific acronym (`OLS`, `SSR`, `MSE`) is exactly the thing a reader
|
|
10
|
+
* benefits from having spelled out, so it belongs in the course's own
|
|
11
|
+
* `content/abbreviations/*.md` table, not here. This baseline only holds
|
|
12
|
+
* abbreviations so common in ordinary English/academic writing that spelling
|
|
13
|
+
* them out on every course would be noise, not help. A course adds a real
|
|
14
|
+
* one via `abbreviations.allow` in `course.config.ts`, the same shape as
|
|
15
|
+
* `operators.allow` / `romanLabels.allow`.
|
|
16
|
+
*/
|
|
17
|
+
export const BASE_ABBREVIATIONS = Object.freeze(
|
|
18
|
+
new Set([
|
|
19
|
+
// dotted Latin/English abbreviations
|
|
20
|
+
'e.g.',
|
|
21
|
+
'i.e.',
|
|
22
|
+
'etc.',
|
|
23
|
+
'cf.',
|
|
24
|
+
'vs.',
|
|
25
|
+
'a.m.',
|
|
26
|
+
'p.m.',
|
|
27
|
+
'a.k.a.',
|
|
28
|
+
'et al.',
|
|
29
|
+
// generic acronyms with no domain-specific meaning in this engine
|
|
30
|
+
'AI',
|
|
31
|
+
'ID',
|
|
32
|
+
'OK',
|
|
33
|
+
'URL',
|
|
34
|
+
'FAQ',
|
|
35
|
+
'US',
|
|
36
|
+
'UK',
|
|
37
|
+
'EU',
|
|
38
|
+
]),
|
|
39
|
+
);
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* The abbreviations a course treats as common, given its `course.config.ts`
|
|
43
|
+
* `abbreviations` block: the shipped baseline, plus `allow`, minus `deny`.
|
|
44
|
+
*
|
|
45
|
+
* @param {{ allow?: readonly string[], deny?: readonly string[] } | undefined} abbreviations
|
|
46
|
+
* @returns {Set<string>}
|
|
47
|
+
*/
|
|
48
|
+
export function effectiveAbbreviationAllowlist(abbreviations) {
|
|
49
|
+
const allow = abbreviations?.allow ?? [];
|
|
50
|
+
const deny = new Set(abbreviations?.deny ?? []);
|
|
51
|
+
const effective = new Set(BASE_ABBREVIATIONS);
|
|
52
|
+
for (const name of allow) effective.add(name);
|
|
53
|
+
for (const name of deny) effective.delete(name);
|
|
54
|
+
return effective;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/** True when `token` needs no abbreviations-table entry for this course. */
|
|
58
|
+
export function isBaseAbbreviation(token, abbreviations) {
|
|
59
|
+
return effectiveAbbreviationAllowlist(abbreviations).has(token);
|
|
60
|
+
}
|
|
@@ -36,3 +36,18 @@ export function isBaseOperator(name: any, operators: any): boolean;
|
|
|
36
36
|
* Matching is case-sensitive.
|
|
37
37
|
*/
|
|
38
38
|
export const BASE_OPERATORS: Readonly<Set<string>>;
|
|
39
|
+
/**
|
|
40
|
+
* The subset of {@link BASE_OPERATORS} that must never appear with a bare
|
|
41
|
+
* (unparenthesized) argument: every trigonometric, inverse-trigonometric, and
|
|
42
|
+
* hyperbolic function, plus the exponential and logarithms (N13). `\sin x`
|
|
43
|
+
* leaves how far the argument extends ambiguous the moment anything follows
|
|
44
|
+
* it (`\sin x + 1` — is the `+1` inside or outside the sine?); `\sin(x)`
|
|
45
|
+
* never does, even for a single-symbol argument. Kept separate from
|
|
46
|
+
* `BASE_OPERATORS`: this is a formatting rule layered on top of the "does it
|
|
47
|
+
* need a notation card" question that set answers, so an operator can be
|
|
48
|
+
* universal (N8) and still owe a parenthesized argument. Enforced by
|
|
49
|
+
* `resolveMathGlyphs` in `math-glyphs.mjs`, which every rendered-math context
|
|
50
|
+
* (lesson body, notation `formula`, assessments, `Diagram` nodes, lab math)
|
|
51
|
+
* already calls.
|
|
52
|
+
*/
|
|
53
|
+
export const PARENTHESIZED_FUNCTIONS: Readonly<Set<string>>;
|
|
@@ -81,6 +81,46 @@ export const BASE_OPERATORS = Object.freeze(
|
|
|
81
81
|
]),
|
|
82
82
|
);
|
|
83
83
|
|
|
84
|
+
/**
|
|
85
|
+
* The subset of {@link BASE_OPERATORS} that must never appear with a bare
|
|
86
|
+
* (unparenthesized) argument: every trigonometric, inverse-trigonometric, and
|
|
87
|
+
* hyperbolic function, plus the exponential and logarithms (N13). `\sin x`
|
|
88
|
+
* leaves how far the argument extends ambiguous the moment anything follows
|
|
89
|
+
* it (`\sin x + 1` — is the `+1` inside or outside the sine?); `\sin(x)`
|
|
90
|
+
* never does, even for a single-symbol argument. Kept separate from
|
|
91
|
+
* `BASE_OPERATORS`: this is a formatting rule layered on top of the "does it
|
|
92
|
+
* need a notation card" question that set answers, so an operator can be
|
|
93
|
+
* universal (N8) and still owe a parenthesized argument. Enforced by
|
|
94
|
+
* `resolveMathGlyphs` in `math-glyphs.mjs`, which every rendered-math context
|
|
95
|
+
* (lesson body, notation `formula`, assessments, `Diagram` nodes, lab math)
|
|
96
|
+
* already calls.
|
|
97
|
+
*/
|
|
98
|
+
export const PARENTHESIZED_FUNCTIONS = Object.freeze(
|
|
99
|
+
new Set([
|
|
100
|
+
// exponential / logarithms
|
|
101
|
+
'exp',
|
|
102
|
+
'log',
|
|
103
|
+
'ln',
|
|
104
|
+
'lg',
|
|
105
|
+
// trigonometric
|
|
106
|
+
'sin',
|
|
107
|
+
'cos',
|
|
108
|
+
'tan',
|
|
109
|
+
'cot',
|
|
110
|
+
'sec',
|
|
111
|
+
'csc',
|
|
112
|
+
// inverse trigonometric
|
|
113
|
+
'arcsin',
|
|
114
|
+
'arccos',
|
|
115
|
+
'arctan',
|
|
116
|
+
// hyperbolic
|
|
117
|
+
'sinh',
|
|
118
|
+
'cosh',
|
|
119
|
+
'tanh',
|
|
120
|
+
'coth',
|
|
121
|
+
]),
|
|
122
|
+
);
|
|
123
|
+
|
|
84
124
|
/**
|
|
85
125
|
* The operators a course treats as common, given its `course.config.ts`
|
|
86
126
|
* `operators` block: the shipped baseline, plus `allow`, minus `deny`.
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The roman labels a course treats as common, given its `course.config.ts`
|
|
3
|
+
* `romanLabels` block: the shipped baseline, plus `allow`, minus `deny`.
|
|
4
|
+
*
|
|
5
|
+
* @param {{ allow?: readonly string[], deny?: readonly string[] } | undefined} romanLabels
|
|
6
|
+
* @returns {Set<string>}
|
|
7
|
+
*/
|
|
8
|
+
export function effectiveRomanLabelAllowlist(romanLabels: {
|
|
9
|
+
allow?: readonly string[];
|
|
10
|
+
deny?: readonly string[];
|
|
11
|
+
} | undefined): Set<string>;
|
|
12
|
+
/** True when `name` needs no notation entry for this course. */
|
|
13
|
+
export function isBaseRomanLabel(name: any, romanLabels: any): boolean;
|
|
14
|
+
/**
|
|
15
|
+
* The base roman-label baseline: `\mathrm{…}` multi-letter labels every
|
|
16
|
+
* reader is assumed to know, so a lesson never has to introduce them with a
|
|
17
|
+
* notation entry. The `\operatorname{…}` counterpart is `base-operators.mjs`
|
|
18
|
+
* (rule N8); this is its `\mathrm{…}` twin (rule N10).
|
|
19
|
+
*
|
|
20
|
+
* Rationale: the completeness gate has no opinion about `\mathrm{…}` bodies —
|
|
21
|
+
* they are a typographic label to KaTeX, not a bindable symbol, and N3
|
|
22
|
+
* requires the format without asking whether the label is ever explained.
|
|
23
|
+
* The `roman-label-explained` consistency check fills that gap: a label a
|
|
24
|
+
* lesson uses is either on this list (or the course's additions to it) or it
|
|
25
|
+
* carries a notation entry the reader can hover.
|
|
26
|
+
*
|
|
27
|
+
* Kept intentionally small — unlike `sin`/`exp`, an upright label is almost
|
|
28
|
+
* always a course-specific named quantity (`KE`, `PV`, `Var`-as-a-label) that
|
|
29
|
+
* genuinely benefits from a hover card, so the shipped baseline holds only
|
|
30
|
+
* the qualifiers that function like an operator's extremum subscript
|
|
31
|
+
* (`x_{\mathrm{opt}}`) rather than naming a quantity of their own. A course
|
|
32
|
+
* adds a real one via `romanLabels.allow` in `course.config.ts`, the same
|
|
33
|
+
* shape as `operators.allow`.
|
|
34
|
+
*/
|
|
35
|
+
export const BASE_ROMAN_LABELS: Readonly<Set<string>>;
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The base roman-label baseline: `\mathrm{…}` multi-letter labels every
|
|
3
|
+
* reader is assumed to know, so a lesson never has to introduce them with a
|
|
4
|
+
* notation entry. The `\operatorname{…}` counterpart is `base-operators.mjs`
|
|
5
|
+
* (rule N8); this is its `\mathrm{…}` twin (rule N10).
|
|
6
|
+
*
|
|
7
|
+
* Rationale: the completeness gate has no opinion about `\mathrm{…}` bodies —
|
|
8
|
+
* they are a typographic label to KaTeX, not a bindable symbol, and N3
|
|
9
|
+
* requires the format without asking whether the label is ever explained.
|
|
10
|
+
* The `roman-label-explained` consistency check fills that gap: a label a
|
|
11
|
+
* lesson uses is either on this list (or the course's additions to it) or it
|
|
12
|
+
* carries a notation entry the reader can hover.
|
|
13
|
+
*
|
|
14
|
+
* Kept intentionally small — unlike `sin`/`exp`, an upright label is almost
|
|
15
|
+
* always a course-specific named quantity (`KE`, `PV`, `Var`-as-a-label) that
|
|
16
|
+
* genuinely benefits from a hover card, so the shipped baseline holds only
|
|
17
|
+
* the qualifiers that function like an operator's extremum subscript
|
|
18
|
+
* (`x_{\mathrm{opt}}`) rather than naming a quantity of their own. A course
|
|
19
|
+
* adds a real one via `romanLabels.allow` in `course.config.ts`, the same
|
|
20
|
+
* shape as `operators.allow`.
|
|
21
|
+
*/
|
|
22
|
+
export const BASE_ROMAN_LABELS = Object.freeze(new Set(['opt']));
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* The roman labels a course treats as common, given its `course.config.ts`
|
|
26
|
+
* `romanLabels` block: the shipped baseline, plus `allow`, minus `deny`.
|
|
27
|
+
*
|
|
28
|
+
* @param {{ allow?: readonly string[], deny?: readonly string[] } | undefined} romanLabels
|
|
29
|
+
* @returns {Set<string>}
|
|
30
|
+
*/
|
|
31
|
+
export function effectiveRomanLabelAllowlist(romanLabels) {
|
|
32
|
+
const allow = romanLabels?.allow ?? [];
|
|
33
|
+
const deny = new Set(romanLabels?.deny ?? []);
|
|
34
|
+
const effective = new Set(BASE_ROMAN_LABELS);
|
|
35
|
+
for (const name of allow) effective.add(name);
|
|
36
|
+
for (const name of deny) effective.delete(name);
|
|
37
|
+
return effective;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** True when `name` needs no notation entry for this course. */
|
|
41
|
+
export function isBaseRomanLabel(name, romanLabels) {
|
|
42
|
+
return effectiveRomanLabelAllowlist(romanLabels).has(name);
|
|
43
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { CourseConventions, CourseOperators } from '../compiler/course-config.js';
|
|
1
|
+
import { CourseConventions, CourseOperators, CourseRomanLabels } from '../compiler/course-config.js';
|
|
2
2
|
import { CurriculumCatalog } from '../curriculum/validation.js';
|
|
3
3
|
import { NotationSpec } from '../compiler/collections.js';
|
|
4
4
|
import { LintIgnore } from './lint-ignore.js';
|
|
@@ -28,12 +28,16 @@ import { NotationRegistry, NotationRegistryInput } from './types.js';
|
|
|
28
28
|
* (h) operator-explained a named operator a lesson uses is on the
|
|
29
29
|
* shipped/course operator allowlist or
|
|
30
30
|
* carries a notation entry (rule N8)
|
|
31
|
+
* (i) roman-label-explained a `\mathrm{…}` multi-letter label a lesson
|
|
32
|
+
* uses is on the shipped/course roman-label
|
|
33
|
+
* allowlist or carries a notation entry
|
|
34
|
+
* (rule N10)
|
|
31
35
|
*
|
|
32
36
|
* Check (d) — the committed per-lesson resolution report — lives in
|
|
33
37
|
* `src/content/resolution-report.ts`.
|
|
34
38
|
*/
|
|
35
39
|
|
|
36
|
-
type ConsistencyCode = 'glyph-unique-in-corpus' | 'notation-units' | 'units-vocab' | 'convention-single-definition' | 'numerals-tagged' | 'weak-local' | 'notation-source-locator' | 'gloss-wants-promoting' | 'card-wants-demoting' | 'gloss-name-shape' | 'operator-explained';
|
|
40
|
+
type ConsistencyCode = 'glyph-unique-in-corpus' | 'notation-units' | 'units-vocab' | 'convention-single-definition' | 'numerals-tagged' | 'weak-local' | 'notation-source-locator' | 'gloss-wants-promoting' | 'card-wants-demoting' | 'gloss-name-shape' | 'operator-explained' | 'roman-label-explained';
|
|
37
41
|
interface ConsistencyDiagnostic {
|
|
38
42
|
readonly code: ConsistencyCode;
|
|
39
43
|
/** Tier 3 — every finding here is a warning until content is brought clean. */
|
|
@@ -56,6 +60,8 @@ interface ConsistencyInput {
|
|
|
56
60
|
readonly conventions: CourseConventions;
|
|
57
61
|
/** This course's edits to the named-operator baseline (rule N8). */
|
|
58
62
|
readonly operators?: CourseOperators;
|
|
63
|
+
/** This course's edits to the roman-label baseline (rule N10). */
|
|
64
|
+
readonly romanLabels?: CourseRomanLabels;
|
|
59
65
|
}
|
|
60
66
|
/**
|
|
61
67
|
* Run every consistency check. The result is sorted deterministically (code,
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { effectiveOperatorAllowlist } from "./base-operators.mjs";
|
|
2
|
-
import {
|
|
2
|
+
import { effectiveRomanLabelAllowlist } from "./base-roman-labels.mjs";
|
|
3
|
+
import { collectOperators, collectRomanLabels } from "./math-glyphs.mjs";
|
|
3
4
|
import { offVocabularyTokens } from "./units-vocab.js";
|
|
4
5
|
const CONVENTION_RE = /\bsign convention\b|\bcash-flow perspective\b|\bholder(?:'s)?(?:\s+\w+){0,2}\s+perspective\b|\bfrom (?:a|one|the|its)(?:\s+\w+){0,3}\s+perspective\b|\breverses? every sign\b|\bpositive (?:means|amounts?)\b/i;
|
|
5
6
|
function stripCode(text) {
|
|
@@ -332,6 +333,50 @@ function checkOperatorExplained(input, out) {
|
|
|
332
333
|
}
|
|
333
334
|
}
|
|
334
335
|
}
|
|
336
|
+
function checkRomanLabelExplained(input, out) {
|
|
337
|
+
const allowlist = effectiveRomanLabelAllowlist(input.romanLabels);
|
|
338
|
+
const byId = new Map(
|
|
339
|
+
input.registry.definitions.map((definition) => [definition.id, definition])
|
|
340
|
+
);
|
|
341
|
+
const bindingsByLesson = new Map(
|
|
342
|
+
input.registry.bundles.map((bundle) => [bundle.lessonId, bundle.bindings])
|
|
343
|
+
);
|
|
344
|
+
for (const lesson of input.notationInput.lessons) {
|
|
345
|
+
const explained = /* @__PURE__ */ new Set();
|
|
346
|
+
const noteEntryLabels = (latex) => {
|
|
347
|
+
for (const label of collectRomanLabels(latex)) {
|
|
348
|
+
explained.add(label.name);
|
|
349
|
+
}
|
|
350
|
+
};
|
|
351
|
+
for (const definition of lesson.localDefinitions) {
|
|
352
|
+
noteEntryLabels(definition.notation);
|
|
353
|
+
}
|
|
354
|
+
for (const binding of bindingsByLesson.get(lesson.lessonId) ?? []) {
|
|
355
|
+
const definition = byId.get(binding.definitionId);
|
|
356
|
+
if (definition) noteEntryLabels(definition.notation);
|
|
357
|
+
}
|
|
358
|
+
const reported = /* @__PURE__ */ new Set();
|
|
359
|
+
for (const span of mathSpans(lesson.body)) {
|
|
360
|
+
for (const label of collectRomanLabels(span.latex)) {
|
|
361
|
+
if (allowlist.has(label.name)) continue;
|
|
362
|
+
if (explained.has(label.name)) continue;
|
|
363
|
+
if (reported.has(label.name)) continue;
|
|
364
|
+
reported.add(label.name);
|
|
365
|
+
if (input.lintIgnore.matches("roman-label-explained", label.name)) {
|
|
366
|
+
continue;
|
|
367
|
+
}
|
|
368
|
+
out.push({
|
|
369
|
+
code: "roman-label-explained",
|
|
370
|
+
severity: "warning",
|
|
371
|
+
lessonId: lesson.lessonId,
|
|
372
|
+
file: lesson.source.file,
|
|
373
|
+
glyph: label.name,
|
|
374
|
+
message: `${lesson.lessonId}: roman label ${JSON.stringify(`\\mathrm{${label.name}}`)} is neither on the course roman-label allowlist nor explained by a notation entry; give it a card or notation.local entry (latex ${JSON.stringify(`\\mathrm{${label.name}}`)}) introduced with [[key]], or add ${JSON.stringify(label.name)} to romanLabels.allow in course.config.ts`
|
|
375
|
+
});
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
}
|
|
335
380
|
const CHECKS = [
|
|
336
381
|
checkGlyphUniqueness,
|
|
337
382
|
checkNotationUnits,
|
|
@@ -342,7 +387,8 @@ const CHECKS = [
|
|
|
342
387
|
checkGlossWantsPromoting,
|
|
343
388
|
checkCardWantsDemoting,
|
|
344
389
|
checkGlossNameShape,
|
|
345
|
-
checkOperatorExplained
|
|
390
|
+
checkOperatorExplained,
|
|
391
|
+
checkRomanLabelExplained
|
|
346
392
|
];
|
|
347
393
|
function runConsistencyChecks(input) {
|
|
348
394
|
const diagnostics = [];
|
|
@@ -363,7 +409,8 @@ function consistencyCounts(diagnostics) {
|
|
|
363
409
|
"gloss-wants-promoting": 0,
|
|
364
410
|
"card-wants-demoting": 0,
|
|
365
411
|
"gloss-name-shape": 0,
|
|
366
|
-
"operator-explained": 0
|
|
412
|
+
"operator-explained": 0,
|
|
413
|
+
"roman-label-explained": 0
|
|
367
414
|
};
|
|
368
415
|
for (const diagnostic of diagnostics) counts[diagnostic.code] += 1;
|
|
369
416
|
return counts;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { KeyedFloatLabelRecord, KeyedFloatLessonInput, KeyedFloatValidation } from './keyed-float-validate.js';
|
|
2
|
+
|
|
3
|
+
/** `validateKeyedFloats` for `<Diagram>` — see `keyed-float-validate.ts`. */
|
|
4
|
+
|
|
5
|
+
type DiagramDiagnosticCode = 'dia-invalid-key' | 'dia-duplicate-key' | 'dia-ref-resolves' | 'dia-key-unused';
|
|
6
|
+
interface DiagramDiagnostic {
|
|
7
|
+
readonly code: DiagramDiagnosticCode;
|
|
8
|
+
readonly severity: 'error' | 'warning';
|
|
9
|
+
readonly message: string;
|
|
10
|
+
readonly file?: string;
|
|
11
|
+
readonly lessonId?: string;
|
|
12
|
+
readonly key?: string;
|
|
13
|
+
}
|
|
14
|
+
type DiagramLabelRecord = KeyedFloatLabelRecord;
|
|
15
|
+
type DiagramLessonInput = KeyedFloatLessonInput;
|
|
16
|
+
interface DiagramValidation extends KeyedFloatValidation {
|
|
17
|
+
readonly diagnostics: readonly DiagramDiagnostic[];
|
|
18
|
+
}
|
|
19
|
+
declare function validateDiagrams(lessons: readonly DiagramLessonInput[]): DiagramValidation;
|
|
20
|
+
|
|
21
|
+
export { type DiagramDiagnostic, type DiagramDiagnosticCode, type DiagramLabelRecord, type DiagramLessonInput, type DiagramValidation, validateDiagrams };
|
|
@@ -45,10 +45,15 @@ interface EquationLessonInput {
|
|
|
45
45
|
readonly file: string;
|
|
46
46
|
readonly body: string;
|
|
47
47
|
/**
|
|
48
|
-
*
|
|
49
|
-
*
|
|
48
|
+
* `{ prefix, offset }` — the part's 1-based ordinal among the sidebar groups,
|
|
49
|
+
* and how many display equations earlier lessons of that part already used.
|
|
50
|
+
* When set, equations are numbered `‹part›.‹offset + n›`; when omitted, the
|
|
51
|
+
* legacy `##`-section scheme. See `docs/authoring/structure.md`.
|
|
50
52
|
*/
|
|
51
|
-
readonly
|
|
53
|
+
readonly numbering?: {
|
|
54
|
+
readonly prefix: number;
|
|
55
|
+
readonly offset: number;
|
|
56
|
+
};
|
|
52
57
|
}
|
|
53
58
|
declare function validateEquations(lessons: readonly EquationLessonInput[]): EquationValidation;
|
|
54
59
|
|
|
@@ -25,7 +25,7 @@ function validateEquations(lessons) {
|
|
|
25
25
|
)) {
|
|
26
26
|
const seen = /* @__PURE__ */ new Set();
|
|
27
27
|
const table = {};
|
|
28
|
-
for (const raw of scanEquationLabels(lesson.body, lesson.
|
|
28
|
+
for (const raw of scanEquationLabels(lesson.body, lesson.numbering)) {
|
|
29
29
|
if (seen.has(raw.key)) {
|
|
30
30
|
diagnostics.push({
|
|
31
31
|
code: "eq-duplicate-key",
|
|
@@ -9,12 +9,28 @@ export function parseEquationRef(raw: any): {
|
|
|
9
9
|
/** Remove every `\label{eq:…}` (and the whitespace it leaves) from a math source. */
|
|
10
10
|
export function stripEquationLabels(latex: any): string;
|
|
11
11
|
/**
|
|
12
|
-
*
|
|
13
|
-
*
|
|
12
|
+
* Normalise the numbering input to `{ prefix, offset }`.
|
|
13
|
+
*
|
|
14
|
+
* `prefix` is the number before the dot — the **part**'s ordinal (its position
|
|
15
|
+
* among the sidebar groups). `offset` is how many display equations already
|
|
16
|
+
* appeared in earlier lessons of that same part, so `n` runs continuously
|
|
17
|
+
* across a part instead of restarting on every page. A bare number is the
|
|
18
|
+
* legacy per-lesson form (`prefix = n`, `offset = 0`) and is still accepted so
|
|
19
|
+
* hand-built trees and unit tests keep working. `undefined`/`null`/`0` ⇒ the
|
|
20
|
+
* `##`-section fallback for non-lesson pages.
|
|
21
|
+
*/
|
|
22
|
+
export function resolveNumbering(input: any): {
|
|
23
|
+
prefix: any;
|
|
24
|
+
offset: number;
|
|
25
|
+
};
|
|
26
|
+
/**
|
|
27
|
+
* The visible number for one display equation. With a `prefix` (the part's
|
|
28
|
+
* ordinal) the scheme is `‹part›.‹offset + indexInLesson›` and `##` headings
|
|
14
29
|
* are ignored; without it, the legacy `##`-section scheme.
|
|
15
30
|
*/
|
|
16
|
-
export function displayEquationNumber({
|
|
17
|
-
|
|
31
|
+
export function displayEquationNumber({ prefix, offset, section, indexInSection, indexInLesson, }: {
|
|
32
|
+
prefix: any;
|
|
33
|
+
offset?: number | undefined;
|
|
18
34
|
section: any;
|
|
19
35
|
indexInSection: any;
|
|
20
36
|
indexInLesson: any;
|
|
@@ -35,58 +51,73 @@ export function stripFencedCode(markdown: any): string;
|
|
|
35
51
|
* `keep`, when given, leaves a component of that name untouched instead of
|
|
36
52
|
* blanking it — `keyed-jsx-floats.mjs` uses this to blank every *other*
|
|
37
53
|
* component while still finding `<Table>`/`<Figure>` tags at the top level.
|
|
54
|
+
*
|
|
55
|
+
* `transparent`, when given, removes only those components' opening/closing
|
|
56
|
+
* tags and exposes their body to the later blanking pass. `<Memo>` and
|
|
57
|
+
* `<ShortNote>` are intentional uses: unlike a worked-example/presentation
|
|
58
|
+
* wrapper, their bodies are details in the lesson's reading order, so numbered
|
|
59
|
+
* equations/floats in them stay in the official sequence. Section headings in
|
|
60
|
+
* a transparent body do not change the surrounding page's section counter.
|
|
38
61
|
*/
|
|
39
|
-
export function stripJsxComponents(markdown: any, keep: any): string;
|
|
62
|
+
export function stripJsxComponents(markdown: any, keep: any, transparent?: any[]): string;
|
|
40
63
|
/**
|
|
41
|
-
* Every
|
|
42
|
-
* its assigned number. `$$…$$` blocks only. With `
|
|
43
|
-
*
|
|
44
|
-
*
|
|
45
|
-
*
|
|
46
|
-
*
|
|
47
|
-
*
|
|
64
|
+
* Every display equation in a Markdown body, keyed or not, in appearance order
|
|
65
|
+
* with its assigned number. `$$…$$` blocks only. With `numbering`
|
|
66
|
+
* (`{ prefix, offset }`, the part's ordinal and the count of display equations
|
|
67
|
+
* in earlier lessons of that part) the number is `‹part›.‹offset + n›`, `n`
|
|
68
|
+
* sequential over every display block in the lesson; without it, `##` headings
|
|
69
|
+
* advance a section counter and the number is `‹section›.‹indexInSection›`.
|
|
70
|
+
* Either way every display block advances the index, so a keyed equation's
|
|
71
|
+
* number reflects its position among *all* display equations, not only the
|
|
72
|
+
* keyed ones.
|
|
48
73
|
*/
|
|
49
|
-
export function
|
|
50
|
-
key: string;
|
|
74
|
+
export function scanDisplayEquations(markdown: any, numbering: any): {
|
|
75
|
+
key: string | null | undefined;
|
|
76
|
+
number: string;
|
|
77
|
+
section: number;
|
|
78
|
+
indexInSection: number;
|
|
79
|
+
}[];
|
|
80
|
+
/** The keyed subset of `scanDisplayEquations` — the manifest builder's view. */
|
|
81
|
+
export function scanEquationLabels(markdown: any, numbering: any): {
|
|
82
|
+
key: string | null | undefined;
|
|
51
83
|
number: string;
|
|
52
84
|
section: number;
|
|
53
85
|
indexInSection: number;
|
|
54
86
|
}[];
|
|
87
|
+
/**
|
|
88
|
+
* How many display equations a lesson body holds. This is what makes `n` run
|
|
89
|
+
* continuously across a part: a lesson's `offset` is the sum of this over every
|
|
90
|
+
* earlier lesson in the same part.
|
|
91
|
+
*/
|
|
92
|
+
export function countDisplayEquations(markdown: any): number;
|
|
55
93
|
/**
|
|
56
94
|
* The mdast equivalent of `scanEquationLabels` for *every* display equation:
|
|
57
95
|
* walk the tree in document order, advancing the section counter on every
|
|
58
96
|
* depth-2 heading and emitting a record for every `math` node. `key` is the
|
|
59
|
-
* `\label{eq:…}` payload when present, else `null`. `
|
|
97
|
+
* `\label{eq:…}` payload when present, else `null`. `numbering` is threaded the
|
|
60
98
|
* same way as in `scanEquationLabels`.
|
|
61
99
|
*/
|
|
62
|
-
export function collectDisplayEquations(tree: any,
|
|
63
|
-
key: string | null;
|
|
64
|
-
number: string;
|
|
65
|
-
section: number;
|
|
66
|
-
indexInSection: number;
|
|
67
|
-
node: any;
|
|
68
|
-
}[];
|
|
100
|
+
export function collectDisplayEquations(tree: any, numbering: any): any[];
|
|
69
101
|
/**
|
|
70
102
|
* The keyed subset of `collectDisplayEquations` — the tree-walk counterpart of
|
|
71
103
|
* `scanEquationLabels`. Numbers reflect position among all display equations.
|
|
72
104
|
*/
|
|
73
|
-
export function collectEquationLabels(tree: any,
|
|
74
|
-
key: string | null;
|
|
75
|
-
number: string;
|
|
76
|
-
section: number;
|
|
77
|
-
indexInSection: number;
|
|
78
|
-
node: any;
|
|
79
|
-
}[];
|
|
105
|
+
export function collectEquationLabels(tree: any, numbering: any): any[];
|
|
80
106
|
/**
|
|
81
107
|
* Equation identity (Step D7, extended F3) — shared, framework-free helpers.
|
|
82
108
|
*
|
|
83
109
|
* Every display equation (`$$…$$`) in a lesson is numbered and given an `id`
|
|
84
|
-
* from appearance order. When the caller supplies
|
|
85
|
-
*
|
|
86
|
-
*
|
|
87
|
-
*
|
|
88
|
-
* the
|
|
89
|
-
*
|
|
110
|
+
* from appearance order. When the caller supplies `numbering`
|
|
111
|
+
* (`{ prefix, offset }` — see `resolveNumbering`), the number is
|
|
112
|
+
* `‹part›.‹offset + n›`: the prefix is the **part**'s ordinal among the sidebar
|
|
113
|
+
* groups, `n` counts every display equation in the lesson, and `offset` carries
|
|
114
|
+
* the count from earlier lessons of the same part so the sequence runs
|
|
115
|
+
* continuously across a part rather than restarting on every page. `##` and
|
|
116
|
+
* `###` headings never affect it — `(2.4)` is the fourth display equation of
|
|
117
|
+
* part 2, wherever in that part it sits. See `docs/authoring/structure.md` for
|
|
118
|
+
* the level names. With no `numbering` the number falls back to the
|
|
119
|
+
* `##`-section scheme `‹section›.‹indexInSection›` (used by hand-built test
|
|
120
|
+
* trees and non-lesson pages). An author additionally
|
|
90
121
|
* marks an equation with a trailing `\label{eq:<key>}` inside its `$$…$$`
|
|
91
122
|
* block (inside math, so the `:` is safe and the form matches amsmath) when
|
|
92
123
|
* they want a *stable* anchor that survives edits and cross-page references:
|