explico 0.7.0 → 0.11.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 +1409 -1
- package/README.md +60 -15
- package/dist/compiler/astro.d.ts +6 -1
- package/dist/compiler/cli.d.ts +54 -3
- package/dist/compiler/cli.js +80 -6
- package/dist/compiler/collections.d.ts +35 -2
- package/dist/compiler/collections.js +84 -8
- package/dist/compiler/course-config.d.ts +3 -194
- package/dist/compiler/course-config.js +78 -5
- package/dist/compiler/exports/standalone-html.d.ts +6 -1
- package/dist/compiler/knowledge-version.js +1 -1
- package/dist/compiler/lesson-derivation.d.ts +27 -10
- package/dist/compiler/lesson-derivation.js +23 -8
- package/dist/compiler/logging.d.ts +14 -1
- package/dist/compiler/logging.js +6 -2
- package/dist/compiler/manifest.d.ts +204 -31
- package/dist/compiler/manifest.js +270 -55
- package/dist/compiler/resolution-report.d.ts +6 -1
- package/dist/course-config-Dsnri9Tk.d.ts +464 -0
- package/dist/reference/base-accents.d.mts +26 -0
- package/dist/reference/base-accents.mjs +31 -0
- package/dist/reference/base-roman-labels.d.mts +2 -2
- package/dist/reference/base-roman-labels.mjs +2 -2
- package/dist/reference/consistency.d.ts +14 -2
- package/dist/reference/consistency.js +108 -4
- package/dist/reference/delimiter-size.d.ts +21 -11
- package/dist/reference/delimiter-size.js +15 -39
- package/dist/reference/diagrams-validate.d.ts +7 -2
- package/dist/reference/diagrams-validate.js +9 -1
- package/dist/reference/equations-validate.d.ts +8 -5
- package/dist/reference/equations-validate.js +1 -0
- package/dist/reference/equations.d.mts +69 -34
- package/dist/reference/equations.mjs +90 -44
- package/dist/reference/examples-validate.d.ts +31 -0
- package/dist/reference/examples-validate.js +48 -0
- package/dist/reference/figures-validate.d.ts +1 -1
- package/dist/reference/figures-validate.js +4 -1
- package/dist/reference/folds-validate.d.ts +33 -0
- package/dist/reference/folds-validate.js +32 -0
- package/dist/reference/gate-abbreviations.d.ts +3 -1
- package/dist/reference/gate-abbreviations.js +3 -2
- package/dist/reference/gate-course-rules.d.ts +32 -0
- package/dist/reference/gate-course-rules.js +85 -0
- package/dist/reference/gate-heading-length.d.ts +5 -2
- package/dist/reference/gate-heading-length.js +47 -23
- package/dist/reference/gate-heading-structure.d.ts +41 -0
- package/dist/reference/gate-heading-structure.js +113 -0
- package/dist/reference/gate-math-punctuation.d.ts +71 -0
- package/dist/reference/gate-math-punctuation.js +251 -0
- package/dist/reference/gate-math.d.ts +44 -6
- package/dist/reference/gate-math.js +63 -10
- package/dist/reference/gate-part-pages.d.ts +50 -0
- package/dist/reference/gate-part-pages.js +118 -0
- package/dist/reference/gate-prose-notation.d.ts +32 -11
- package/dist/reference/gate-prose-notation.js +30 -8
- package/dist/reference/katex-delimiters.d.mts +15 -0
- package/dist/reference/katex-delimiters.mjs +138 -0
- package/dist/reference/katex-options.mjs +4 -0
- package/dist/reference/keyed-float-validate.d.ts +27 -10
- package/dist/reference/keyed-float-validate.js +15 -1
- package/dist/reference/keyed-jsx-floats.d.mts +11 -5
- package/dist/reference/keyed-jsx-floats.mjs +73 -20
- package/dist/reference/knowledge-checks-validate.d.ts +1 -1
- package/dist/reference/knowledge-checks.d.mts +3 -1
- package/dist/reference/knowledge-checks.mjs +9 -4
- package/dist/reference/lab-math-scope.d.ts +1 -0
- package/dist/reference/lab-math-slot.d.ts +34 -0
- package/dist/reference/lab-math-slot.js +6 -0
- package/dist/reference/math-glyphs.d.mts +80 -4
- package/dist/reference/math-glyphs.mjs +588 -39
- package/dist/reference/page-notation.d.ts +6 -1
- package/dist/reference/raw-floats-validate.js +1 -1
- package/dist/reference/raw-floats.mjs +1 -1
- package/dist/reference/rehype-heading-anchors.d.mts +14 -0
- package/dist/reference/rehype-heading-anchors.mjs +116 -17
- package/dist/reference/rehype-table-header-labels.d.mts +6 -1
- package/dist/reference/rehype-table-header-labels.mjs +21 -3
- package/dist/reference/remark-asides.d.mts +3 -0
- package/dist/reference/remark-asides.mjs +180 -0
- package/dist/reference/remark-memo.d.mts +3 -1
- package/dist/reference/remark-memo.mjs +42 -11
- package/dist/reference/remark-notation.d.mts +60 -10
- package/dist/reference/remark-notation.mjs +387 -36
- package/dist/reference/render-lab-math.d.ts +4 -26
- package/dist/reference/render-lab-math.js +5 -5
- package/dist/reference/sections-validate.d.ts +27 -0
- package/dist/reference/sections-validate.js +41 -0
- package/dist/reference/sections.d.mts +97 -0
- package/dist/reference/sections.mjs +306 -0
- package/dist/reference/tables-validate.d.ts +1 -1
- package/dist/reference/types.d.ts +24 -1
- package/dist/reference/unit-words.d.ts +37 -0
- package/dist/reference/unit-words.js +170 -0
- package/dist/rules/catalog.d.ts +176 -0
- package/dist/rules/catalog.js +983 -0
- package/dist/rules/course-rules.d.ts +115 -0
- package/dist/rules/course-rules.js +292 -0
- package/dist/rules/course-rules.schema.json +138 -0
- package/dist/rules/in-effect.d.ts +53 -0
- package/dist/rules/in-effect.js +142 -0
- package/dist/rules/rules.schema.json +1072 -0
- package/dist/rules/settings.d.ts +3 -0
- package/dist/rules/settings.js +290 -0
- package/dist/rules/waivers.d.ts +53 -0
- package/dist/rules/waivers.js +132 -0
- package/dist/session/PreferenceStore.d.ts +5 -0
- package/dist/session/PreferenceStore.js +9 -0
- package/docs/VERSIONING.md +2 -2
- package/docs/authoring/README.md +232 -40
- package/docs/authoring/agents.md +55 -9
- package/docs/authoring/components.md +178 -22
- package/docs/authoring/migrating.md +1324 -2
- package/docs/authoring/notation.md +616 -344
- package/docs/authoring/prose.md +551 -0
- package/docs/authoring/retired.md +35 -0
- package/docs/authoring/structure-rules.md +422 -40
- package/docs/authoring/structure.md +445 -62
- package/docs/authoring/versioning.md +20 -28
- package/docs/authoring/visualizations.md +142 -20
- package/docs/katex-bracket-scaling.md +191 -0
- package/package.json +3 -2
- package/src/compiler/cli.ts +139 -6
- package/src/compiler/collections.ts +144 -10
- package/src/compiler/course-config.ts +273 -34
- package/src/compiler/knowledge-version.ts +1 -1
- package/src/compiler/lesson-derivation.ts +54 -15
- package/src/compiler/logging.ts +21 -4
- package/src/compiler/manifest.ts +573 -104
- package/src/components/BlockHeading.astro +113 -0
- package/src/components/CourseContents.astro +221 -0
- package/src/components/CurriculumMap.astro +5 -6
- package/src/components/FloatIndex.astro +200 -0
- package/src/components/HoverPanel.astro +431 -79
- package/src/components/PartBridge.astro +27 -0
- package/src/components/PartOverview.astro +401 -0
- package/src/components/PartScope.astro +27 -0
- package/src/components/abbreviation/AbbreviationLayer.astro +49 -51
- package/src/components/assessments/AssessmentRunner.tsx +8 -5
- package/src/components/assessments/AssessmentSet.astro +23 -26
- package/src/components/block-heading.ts +146 -0
- package/src/components/card-arrow.ts +243 -0
- package/src/components/card-drag.ts +631 -0
- package/src/components/card-jump.ts +45 -0
- package/src/components/card-manager.ts +1589 -0
- package/src/components/card-memory.ts +177 -0
- package/src/components/card-placement.ts +504 -0
- package/src/components/card-side.ts +336 -0
- package/src/components/card-travel.ts +197 -0
- package/src/components/citation/CitationLayer.astro +77 -76
- package/src/components/diagram/Diagram.astro +161 -316
- package/src/components/disclosure/course-disclosure.ts +24 -0
- package/src/components/disclosure/hover-preview.ts +122 -42
- package/src/components/disclosure/persisted-disclosure.ts +3 -3
- package/src/components/disclosure/print-as-shown.ts +252 -0
- package/src/components/examples/CompactExample.astro +3 -1
- package/src/components/examples/CompactExamples.astro +101 -94
- package/src/components/figure/Figure.astro +174 -274
- package/src/components/figure/FloatCaption.astro +87 -0
- package/src/components/figure/figure-state.ts +58 -0
- package/src/components/figure/figure-tools.ts +87 -0
- package/src/components/float-name.ts +101 -0
- package/src/components/fold/Fold.astro +125 -0
- package/src/components/fold/fold-id.ts +40 -0
- package/src/components/memo/Memo.astro +162 -231
- package/src/components/memo/ShortNote.astro +6 -6
- package/src/components/notation/NotationFormula.astro +5 -1
- package/src/components/notation/NotationGlossary.astro +24 -14
- package/src/components/notation/NotationLayer.astro +169 -162
- package/src/components/notation/NotationMath.astro +10 -1
- package/src/components/notation/render-notation-math.ts +111 -10
- package/src/components/reference/FloatRefLayer.astro +485 -423
- package/src/components/starlight/AboutPanel.astro +22 -6
- package/src/components/starlight/CollapsibleSections.astro +207 -93
- package/src/components/starlight/DiagramEnhancer.astro +15 -138
- package/src/components/starlight/HeadingAnchorEnhancer.astro +193 -1
- package/src/components/starlight/KeyedFloatEnhancer.astro +100 -9
- package/src/components/starlight/LayoutHeader.astro +298 -19
- package/src/components/starlight/LayoutPageSidebar.astro +99 -12
- package/src/components/starlight/LayoutPageTitle.astro +120 -0
- package/src/components/starlight/LayoutSidebar.astro +229 -11
- package/src/components/starlight/LessonExportActions.astro +53 -72
- package/src/components/starlight/LessonFooter.astro +26 -4
- package/src/components/starlight/section-parts.ts +15 -2
- package/src/components/starlight/section-summary.ts +181 -0
- package/src/components/table/Table.astro +95 -76
- package/src/components/table/TableOverflowExpander.astro +65 -28
- package/src/content-config.ts +40 -4
- package/src/reference/base-accents.mjs +31 -0
- package/src/reference/base-roman-labels.mjs +2 -2
- package/src/reference/consistency.ts +176 -5
- package/src/reference/delimiter-size.ts +40 -61
- package/src/reference/diagrams-validate.ts +17 -1
- package/src/reference/equations-validate.ts +10 -5
- package/src/reference/equations.mjs +90 -44
- package/src/reference/examples-validate.ts +97 -0
- package/src/reference/figures-validate.ts +6 -1
- package/src/reference/folds-validate.ts +65 -0
- package/src/reference/gate-abbreviations.ts +3 -2
- package/src/reference/gate-course-rules.ts +134 -0
- package/src/reference/gate-heading-length.ts +68 -37
- package/src/reference/gate-heading-structure.ts +193 -0
- package/src/reference/gate-math-punctuation.ts +381 -0
- package/src/reference/gate-math.ts +119 -12
- package/src/reference/gate-part-pages.ts +217 -0
- package/src/reference/gate-prose-notation.ts +74 -13
- package/src/reference/katex-delimiters.mjs +138 -0
- package/src/reference/katex-options.mjs +4 -0
- package/src/reference/keyed-float-validate.ts +45 -11
- package/src/reference/keyed-jsx-floats.mjs +73 -20
- package/src/reference/knowledge-checks-validate.ts +1 -1
- package/src/reference/knowledge-checks.mjs +9 -4
- package/src/reference/lab-math-slot.ts +36 -0
- package/src/reference/math-glyphs.mjs +588 -39
- package/src/reference/raw-floats-validate.ts +1 -1
- package/src/reference/raw-floats.mjs +1 -1
- package/src/reference/rehype-heading-anchors.mjs +116 -17
- package/src/reference/rehype-table-header-labels.mjs +21 -3
- package/src/reference/remark-asides.mjs +180 -0
- package/src/reference/remark-memo.mjs +42 -11
- package/src/reference/remark-notation.mjs +387 -36
- package/src/reference/render-lab-math.ts +17 -32
- package/src/reference/sections-validate.ts +81 -0
- package/src/reference/sections.mjs +306 -0
- package/src/reference/tables-validate.ts +2 -1
- package/src/reference/types.ts +24 -0
- package/src/reference/unit-words.ts +233 -0
- package/src/rules/catalog.ts +1298 -0
- package/src/rules/course-rules.ts +456 -0
- package/src/rules/in-effect.ts +235 -0
- package/src/rules/settings.ts +448 -0
- package/src/rules/waivers.ts +249 -0
- package/src/session/PreferenceStore.ts +15 -0
- package/src/styles/global.css +1471 -163
- package/src/styles/surfaces.css +182 -0
- package/src/components/hover-panel.ts +0 -339
- package/src/components/popup-stack.ts +0 -345
|
@@ -1,6 +1,16 @@
|
|
|
1
|
+
import { effectiveAccentAllowlist } from "./base-accents.mjs";
|
|
1
2
|
import { effectiveOperatorAllowlist } from "./base-operators.mjs";
|
|
2
3
|
import { effectiveRomanLabelAllowlist } from "./base-roman-labels.mjs";
|
|
3
|
-
import {
|
|
4
|
+
import { stripEquationLabels } from "./equations.mjs";
|
|
5
|
+
import { pageScope } from "./gate-math.js";
|
|
6
|
+
import {
|
|
7
|
+
collectDecorations,
|
|
8
|
+
collectOperators,
|
|
9
|
+
collectRomanLabels,
|
|
10
|
+
decorationOfEntry,
|
|
11
|
+
operatorEntryLatex,
|
|
12
|
+
resolveMathGlyphs
|
|
13
|
+
} from "./math-glyphs.mjs";
|
|
4
14
|
import { offVocabularyTokens } from "./units-vocab.js";
|
|
5
15
|
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;
|
|
6
16
|
function stripCode(text) {
|
|
@@ -10,7 +20,7 @@ function mathSpans(body) {
|
|
|
10
20
|
const searchable = stripCode(body);
|
|
11
21
|
const spans = [];
|
|
12
22
|
for (const match of searchable.matchAll(/\$\$([\s\S]+?)\$\$/g)) {
|
|
13
|
-
spans.push({ latex: match[1] ?? "" });
|
|
23
|
+
spans.push({ latex: stripEquationLabels(match[1] ?? "") });
|
|
14
24
|
}
|
|
15
25
|
for (const match of searchable.replace(/\$\$([\s\S]+?)\$\$/g, (block) => block.replace(/[^\n]/g, " ")).matchAll(/(?<![\\$])\$(?!\s)([^$\n]+?)(?<![\\\s])\$(?!\d)/g)) {
|
|
16
26
|
spans.push({ latex: match[1] ?? "" });
|
|
@@ -335,6 +345,94 @@ function checkRomanLabelExplained(input, out) {
|
|
|
335
345
|
}
|
|
336
346
|
}
|
|
337
347
|
}
|
|
348
|
+
function sharedScopeByKey(input) {
|
|
349
|
+
return new Map(
|
|
350
|
+
input.notationInput.sharedDefinitions.map((definition) => [
|
|
351
|
+
definition.key,
|
|
352
|
+
{ key: definition.key, notation: definition.notation }
|
|
353
|
+
])
|
|
354
|
+
);
|
|
355
|
+
}
|
|
356
|
+
function checkAccentExplained(input, out) {
|
|
357
|
+
const allowlist = effectiveAccentAllowlist(input.accents);
|
|
358
|
+
const sharedByKey = sharedScopeByKey(input);
|
|
359
|
+
for (const lesson of input.notationInput.lessons) {
|
|
360
|
+
const scope = pageScope(lesson, sharedByKey);
|
|
361
|
+
const suspended = (lesson.suspended ?? []).map((item) => item.operator);
|
|
362
|
+
const reported = /* @__PURE__ */ new Set();
|
|
363
|
+
for (const span of mathSpans(lesson.body)) {
|
|
364
|
+
let decorations;
|
|
365
|
+
try {
|
|
366
|
+
({ decorations } = resolveMathGlyphs(span.latex, scope, { suspended }));
|
|
367
|
+
} catch {
|
|
368
|
+
continue;
|
|
369
|
+
}
|
|
370
|
+
for (const decoration of decorations) {
|
|
371
|
+
if (decoration.wholeForm !== null || decoration.operator !== null) continue;
|
|
372
|
+
if (suspended.includes(decoration.name)) continue;
|
|
373
|
+
if (allowlist.has(decoration.name)) continue;
|
|
374
|
+
if (reported.has(decoration.name)) continue;
|
|
375
|
+
reported.add(decoration.name);
|
|
376
|
+
if (input.lintIgnore.matches("accent-explained", decoration.name)) {
|
|
377
|
+
continue;
|
|
378
|
+
}
|
|
379
|
+
out.push({
|
|
380
|
+
code: "accent-explained",
|
|
381
|
+
severity: "warning",
|
|
382
|
+
lessonId: lesson.lessonId,
|
|
383
|
+
file: lesson.source.file,
|
|
384
|
+
glyph: decoration.name,
|
|
385
|
+
message: `${lesson.lessonId}: decoration ${JSON.stringify(decoration.name)} (in ${JSON.stringify(decoration.token)}) is an operator that is neither universal for this course nor explained; give it an operator entry (latex ${JSON.stringify(operatorEntryLatex(decoration.name))}) introduced with [[key]] or in notation.local, give the whole form its own entry (latex ${JSON.stringify(decoration.token)}), or add ${JSON.stringify(decoration.name)} to rules.N22.allow`
|
|
386
|
+
});
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
function checkOperatorSuspension(input, out) {
|
|
392
|
+
const allowlist = effectiveAccentAllowlist(input.accents);
|
|
393
|
+
const sharedByKey = sharedScopeByKey(input);
|
|
394
|
+
for (const lesson of input.notationInput.lessons) {
|
|
395
|
+
const suspensions = lesson.suspended ?? [];
|
|
396
|
+
for (const { operator, reason } of suspensions) {
|
|
397
|
+
const code = reason === void 0 ? "operator-suspension-reason" : "operator-suspended";
|
|
398
|
+
if (input.lintIgnore.matches(code, operator)) continue;
|
|
399
|
+
out.push({
|
|
400
|
+
code,
|
|
401
|
+
severity: reason === void 0 ? "warning" : "info",
|
|
402
|
+
lessonId: lesson.lessonId,
|
|
403
|
+
file: lesson.source.file,
|
|
404
|
+
glyph: operator,
|
|
405
|
+
message: reason === void 0 ? `${lesson.lessonId}: notation.suspend suspends ${JSON.stringify(operator)} with no reason; say why it is not an operator on this page (reason: \u2026)` : `${lesson.lessonId}: ${JSON.stringify(operator)} is not an operator on this page (notation.suspend): ${reason}`
|
|
406
|
+
});
|
|
407
|
+
}
|
|
408
|
+
const suspended = new Set(suspensions.map((item) => item.operator));
|
|
409
|
+
const scope = pageScope(lesson, sharedByKey);
|
|
410
|
+
const explained = new Set(
|
|
411
|
+
scope.map((entry) => decorationOfEntry(entry.notation)).filter((name) => name !== void 0)
|
|
412
|
+
);
|
|
413
|
+
for (const entry of scope) {
|
|
414
|
+
if (decorationOfEntry(entry.notation) !== void 0) continue;
|
|
415
|
+
const names = new Set(
|
|
416
|
+
collectDecorations(entry.notation).map((decoration) => decoration.name)
|
|
417
|
+
);
|
|
418
|
+
for (const name of names) {
|
|
419
|
+
if (suspended.has(name)) continue;
|
|
420
|
+
const how = explained.has(name) ? "explained by an operator entry" : allowlist.has(name) ? "universal for this course" : void 0;
|
|
421
|
+
if (how === void 0) continue;
|
|
422
|
+
if (input.lintIgnore.matches("operator-shadowed", entry.key)) continue;
|
|
423
|
+
out.push({
|
|
424
|
+
code: "operator-shadowed",
|
|
425
|
+
severity: "info",
|
|
426
|
+
lessonId: lesson.lessonId,
|
|
427
|
+
file: lesson.source.file,
|
|
428
|
+
key: entry.key,
|
|
429
|
+
glyph: name,
|
|
430
|
+
message: `${lesson.lessonId}: ${entry.key} (${entry.notation}) has its own entry, so its card wins over the ${JSON.stringify(name)} operator (${how}) for that form; if ${JSON.stringify(name)} never means the operator on this page, suspend it (notation.suspend)`
|
|
431
|
+
});
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
}
|
|
338
436
|
const CHECKS = [
|
|
339
437
|
checkGlyphUniqueness,
|
|
340
438
|
checkNotationUnits,
|
|
@@ -345,7 +443,9 @@ const CHECKS = [
|
|
|
345
443
|
checkCardWantsDemoting,
|
|
346
444
|
checkGlossNameShape,
|
|
347
445
|
checkOperatorExplained,
|
|
348
|
-
checkRomanLabelExplained
|
|
446
|
+
checkRomanLabelExplained,
|
|
447
|
+
checkAccentExplained,
|
|
448
|
+
checkOperatorSuspension
|
|
349
449
|
];
|
|
350
450
|
function runConsistencyChecks(input) {
|
|
351
451
|
const diagnostics = [];
|
|
@@ -366,7 +466,11 @@ function consistencyCounts(diagnostics) {
|
|
|
366
466
|
"card-wants-demoting": 0,
|
|
367
467
|
"gloss-name-shape": 0,
|
|
368
468
|
"operator-explained": 0,
|
|
369
|
-
"roman-label-explained": 0
|
|
469
|
+
"roman-label-explained": 0,
|
|
470
|
+
"accent-explained": 0,
|
|
471
|
+
"operator-suspended": 0,
|
|
472
|
+
"operator-suspension-reason": 0,
|
|
473
|
+
"operator-shadowed": 0
|
|
370
474
|
};
|
|
371
475
|
for (const diagnostic of diagnostics) counts[diagnostic.code] += 1;
|
|
372
476
|
return counts;
|
|
@@ -1,15 +1,21 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Rule N17: nested scalable
|
|
2
|
+
* Rule N17: a nested scalable delimiter is sized by the engine, never by hand.
|
|
3
|
+
*
|
|
4
|
+
* The engine installs a KaTeX whose `\delimitershortfall` is `-1pt`
|
|
5
|
+
* (`katex-delimiters.mjs`), so a `\left`/`\right` pair is always
|
|
6
|
+
* strictly taller than what it wraps. Nesting depth therefore shows up on its
|
|
7
|
+
* own, and a hand-picked `\bigl`…`\Biggr` inside a nest only replaces a size
|
|
8
|
+
* that tracks the content with a fixed glyph that does not.
|
|
3
9
|
*
|
|
4
10
|
* This deliberately parses only sizing syntax. It does not decide whether a
|
|
5
11
|
* parenthesis, bracket, or brace is semantic, and it never recommends changing
|
|
6
|
-
* one delimiter shape into another
|
|
7
|
-
*
|
|
12
|
+
* one delimiter shape into another — N17's other half, that a delimiter's shape
|
|
13
|
+
* is fixed by its meaning and never alternates to show depth, stays a
|
|
14
|
+
* review-only authoring concern (which is why N17 is `partial`).
|
|
8
15
|
*/
|
|
9
|
-
declare const DELIMITER_SIZE_DIAGNOSTIC_CODE: "delimiter-size
|
|
16
|
+
declare const DELIMITER_SIZE_DIAGNOSTIC_CODE: "delimiter-manual-size";
|
|
10
17
|
type DelimiterSizeName = 'automatic' | 'big' | 'Big' | 'bigg' | 'Bigg';
|
|
11
18
|
interface DelimiterSizeSuggestion {
|
|
12
|
-
readonly size: Exclude<DelimiterSizeName, 'automatic' | 'big'>;
|
|
13
19
|
readonly open: string;
|
|
14
20
|
readonly close: string;
|
|
15
21
|
readonly latex: string;
|
|
@@ -20,15 +26,19 @@ interface DelimiterSizeDiagnostic {
|
|
|
20
26
|
readonly offset: number;
|
|
21
27
|
readonly line: number;
|
|
22
28
|
readonly column: number;
|
|
23
|
-
|
|
24
|
-
readonly
|
|
25
|
-
readonly suggestion
|
|
29
|
+
/** The hand-picked size that should become `\left`/`\right`. */
|
|
30
|
+
readonly size: Exclude<DelimiterSizeName, 'automatic'>;
|
|
31
|
+
readonly suggestion: DelimiterSizeSuggestion;
|
|
26
32
|
readonly message: string;
|
|
27
33
|
}
|
|
28
34
|
/**
|
|
29
|
-
* Warn when a
|
|
30
|
-
*
|
|
31
|
-
*
|
|
35
|
+
* Warn when a pair that takes part in a nest of scalable delimiters is sized
|
|
36
|
+
* by hand instead of by `\left`/`\right`.
|
|
37
|
+
*
|
|
38
|
+
* A pair takes part in a nest when it contains, or sits inside, another pair
|
|
39
|
+
* this linter tracks. A lone hand-sized pair around otherwise bare content —
|
|
40
|
+
* `\bigl(x(t),\ y(t)\bigr)` for a tuple — is a deliberate typographic
|
|
41
|
+
* choice at a single level, has no hierarchy to get wrong, and is left alone.
|
|
32
42
|
*
|
|
33
43
|
* Only `()`, `[]`, and `\{\}` are parsed. Bare delimiters are intentionally
|
|
34
44
|
* ignored: N15 owns that separate error, while ordinary semantic notation
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
const DELIMITER_SIZE_DIAGNOSTIC_CODE = "delimiter-size
|
|
1
|
+
const DELIMITER_SIZE_DIAGNOSTIC_CODE = "delimiter-manual-size";
|
|
2
2
|
const SIZE_SPECS = [
|
|
3
3
|
{
|
|
4
4
|
name: "automatic",
|
|
@@ -40,9 +40,6 @@ const COMMAND_SPECS = SIZE_SPECS.flatMap((spec) => [
|
|
|
40
40
|
{ ...spec, command: spec.openCommand, tokenKind: "open" },
|
|
41
41
|
{ ...spec, command: spec.closeCommand, tokenKind: "close" }
|
|
42
42
|
]).sort((left, right) => right.command.length - left.command.length);
|
|
43
|
-
const SUGGESTION_BY_RANK = new Map(
|
|
44
|
-
SIZE_SPECS.filter((spec) => spec.rank >= 2).map((spec) => [spec.rank, spec])
|
|
45
|
-
);
|
|
46
43
|
function delimiterAt(latex, offset, kind) {
|
|
47
44
|
const candidates = kind === "open" ? ["\\{", "(", "["] : ["\\}", ")", "]"];
|
|
48
45
|
for (const delimiter of candidates) {
|
|
@@ -120,55 +117,34 @@ function lineAndColumn(source, offset) {
|
|
|
120
117
|
column: offset - lastNewline
|
|
121
118
|
};
|
|
122
119
|
}
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
const spec = SUGGESTION_BY_RANK.get(requiredRank);
|
|
129
|
-
if (!close || !spec || spec.name === "automatic" || spec.name === "big") {
|
|
130
|
-
return void 0;
|
|
131
|
-
}
|
|
132
|
-
return {
|
|
133
|
-
size: spec.name,
|
|
134
|
-
open: `${spec.openCommand}${pair.open.delimiter}`,
|
|
135
|
-
close: `${spec.closeCommand}${close.delimiter}`,
|
|
136
|
-
latex: `${spec.openCommand}${pair.open.delimiter} ... ${spec.closeCommand}${close.delimiter}`
|
|
137
|
-
};
|
|
120
|
+
const SPEC_BY_SIZE = new Map(SIZE_SPECS.map((spec) => [spec.name, spec]));
|
|
121
|
+
function suggestionFor(pair) {
|
|
122
|
+
const open = `\\left${pair.open.delimiter}`;
|
|
123
|
+
const close = `\\right${pair.close?.delimiter ?? pair.open.delimiter}`;
|
|
124
|
+
return { open, close, latex: `${open} ... ${close}` };
|
|
138
125
|
}
|
|
139
126
|
function lintDelimiterSizes(latex) {
|
|
140
127
|
const roots = parseDelimiterPairs(latex);
|
|
141
128
|
if (!roots) return [];
|
|
142
129
|
const diagnostics = [];
|
|
143
|
-
const
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
for (const child of pair.children) {
|
|
147
|
-
const childRequired = assess(child);
|
|
148
|
-
if (childRequired > largestNestedRequirement) {
|
|
149
|
-
largestNestedRequirement = childRequired;
|
|
150
|
-
representativeNestedSize = sizeName(childRequired);
|
|
151
|
-
}
|
|
152
|
-
}
|
|
153
|
-
const requiredRank = largestNestedRequirement === 0 ? pair.open.rank : largestNestedRequirement + 1;
|
|
154
|
-
pair.requiredRank = Math.max(pair.open.rank, requiredRank);
|
|
155
|
-
if (largestNestedRequirement > 0 && pair.open.rank < requiredRank) {
|
|
130
|
+
const visit = (pair, nested) => {
|
|
131
|
+
const inNest = nested || pair.children.length > 0;
|
|
132
|
+
if (inNest && pair.open.pairKind === "manual") {
|
|
156
133
|
const location = lineAndColumn(latex, pair.open.offset);
|
|
157
|
-
const suggestion = suggestionFor(pair
|
|
134
|
+
const suggestion = suggestionFor(pair);
|
|
158
135
|
diagnostics.push({
|
|
159
136
|
code: DELIMITER_SIZE_DIAGNOSTIC_CODE,
|
|
160
137
|
severity: "warning",
|
|
161
138
|
offset: pair.open.offset,
|
|
162
139
|
...location,
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
message: suggestion ? `Outer delimiters are not larger than their nested pair. Use ${suggestion.latex}; preserve the original opening and closing delimiter shapes (rule N17).` : `Outer delimiters are not larger than their nested pair, and no larger standard manual delimiter size remains. Preserve the original opening and closing shapes and restructure the expression if necessary (rule N17).`
|
|
140
|
+
size: pair.open.size,
|
|
141
|
+
suggestion,
|
|
142
|
+
message: `A nested delimiter pair is sized by hand with ${SPEC_BY_SIZE.get(pair.open.size)?.openCommand ?? pair.open.size}${pair.open.delimiter}. Use ${suggestion.latex} and let the engine size the nest; preserve the original opening and closing delimiter shapes (rule N17).`
|
|
167
143
|
});
|
|
168
144
|
}
|
|
169
|
-
|
|
145
|
+
for (const child of pair.children) visit(child, true);
|
|
170
146
|
};
|
|
171
|
-
for (const root of roots)
|
|
147
|
+
for (const root of roots) visit(root, false);
|
|
172
148
|
return diagnostics.sort((left, right) => left.offset - right.offset);
|
|
173
149
|
}
|
|
174
150
|
export {
|
|
@@ -2,7 +2,7 @@ import { KeyedFloatLabelRecord, KeyedFloatLessonInput, KeyedFloatValidation } fr
|
|
|
2
2
|
|
|
3
3
|
/** `validateKeyedFloats` for `<Diagram>` — see `keyed-float-validate.ts`. */
|
|
4
4
|
|
|
5
|
-
type DiagramDiagnosticCode = 'dia-invalid-key' | 'dia-duplicate-key' | 'dia-ref-resolves' | 'dia-key-unused';
|
|
5
|
+
type DiagramDiagnosticCode = 'dia-invalid-key' | 'dia-duplicate-key' | 'dia-ref-resolves' | 'dia-key-unused' | 'dia-removed-prop';
|
|
6
6
|
interface DiagramDiagnostic {
|
|
7
7
|
readonly code: DiagramDiagnosticCode;
|
|
8
8
|
readonly severity: 'error' | 'warning' | 'info';
|
|
@@ -16,6 +16,11 @@ type DiagramLessonInput = KeyedFloatLessonInput;
|
|
|
16
16
|
interface DiagramValidation extends KeyedFloatValidation {
|
|
17
17
|
readonly diagnostics: readonly DiagramDiagnostic[];
|
|
18
18
|
}
|
|
19
|
+
/**
|
|
20
|
+
* The fix for a compaction prop: a diagram is always its graph and then its
|
|
21
|
+
* caption line (docs/plans/figures-and-short-captions.md, decision 7).
|
|
22
|
+
*/
|
|
23
|
+
declare const DIAGRAM_NO_LONGER_FOLDS = "a diagram no longer folds by itself \u2014 delete the prop. To let a reader fold it away, wrap it in a `<Fold>` or put it in a collapsible section.";
|
|
19
24
|
declare function validateDiagrams(lessons: readonly DiagramLessonInput[]): DiagramValidation;
|
|
20
25
|
|
|
21
|
-
export { type DiagramDiagnostic, type DiagramDiagnosticCode, type DiagramLabelRecord, type DiagramLessonInput, type DiagramValidation, validateDiagrams };
|
|
26
|
+
export { DIAGRAM_NO_LONGER_FOLDS, type DiagramDiagnostic, type DiagramDiagnosticCode, type DiagramLabelRecord, type DiagramLessonInput, type DiagramValidation, validateDiagrams };
|
|
@@ -1,13 +1,21 @@
|
|
|
1
1
|
import {
|
|
2
2
|
validateKeyedFloats
|
|
3
3
|
} from "./keyed-float-validate.js";
|
|
4
|
+
const DIAGRAM_NO_LONGER_FOLDS = "a diagram no longer folds by itself \u2014 delete the prop. To let a reader fold it away, wrap it in a `<Fold>` or put it in a collapsible section.";
|
|
4
5
|
function validateDiagrams(lessons) {
|
|
5
6
|
return validateKeyedFloats(lessons, {
|
|
6
7
|
tagName: "Diagram",
|
|
7
8
|
prefix: "dia",
|
|
8
|
-
noun: "diagram"
|
|
9
|
+
noun: "diagram",
|
|
10
|
+
removedProps: {
|
|
11
|
+
label: "rename it `caption` (shown below the diagram with its number), and add an optional `short` caption if the caption is long.",
|
|
12
|
+
title: "a diagram is named by its `caption` (shown below it with its number) and an optional `short` caption.",
|
|
13
|
+
defaultOpen: DIAGRAM_NO_LONGER_FOLDS,
|
|
14
|
+
rememberState: DIAGRAM_NO_LONGER_FOLDS
|
|
15
|
+
}
|
|
9
16
|
});
|
|
10
17
|
}
|
|
11
18
|
export {
|
|
19
|
+
DIAGRAM_NO_LONGER_FOLDS,
|
|
12
20
|
validateDiagrams
|
|
13
21
|
};
|
|
@@ -12,6 +12,8 @@
|
|
|
12
12
|
interface EquationLabelRecord {
|
|
13
13
|
readonly key: string;
|
|
14
14
|
readonly number: string;
|
|
15
|
+
/** The equation's `\caption{…}` short caption, when it has one. */
|
|
16
|
+
readonly short?: string;
|
|
15
17
|
readonly lessonId: string;
|
|
16
18
|
readonly slug: string;
|
|
17
19
|
readonly section: number;
|
|
@@ -45,13 +47,14 @@ interface EquationLessonInput {
|
|
|
45
47
|
readonly file: string;
|
|
46
48
|
readonly body: string;
|
|
47
49
|
/**
|
|
48
|
-
* `{ prefix, offset }` — the
|
|
49
|
-
*
|
|
50
|
-
*
|
|
51
|
-
* legacy `##`-section
|
|
50
|
+
* `{ prefix, offset }` — the lesson's float-number prefix (`‹part›.‹chapter›`,
|
|
51
|
+
* or `‹part›` under `numbering.floats: "part"`) and how many display
|
|
52
|
+
* equations precede the lesson in that scope. When set, equations are
|
|
53
|
+
* numbered `‹prefix›.‹offset + n›`; when omitted, the legacy `##`-section
|
|
54
|
+
* scheme. See `docs/authoring/structure.md`.
|
|
52
55
|
*/
|
|
53
56
|
readonly numbering?: {
|
|
54
|
-
readonly prefix: number;
|
|
57
|
+
readonly prefix: number | string;
|
|
55
58
|
readonly offset: number;
|
|
56
59
|
};
|
|
57
60
|
}
|
|
@@ -1,3 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The short caption a display equation carries, trimmed, or `null` when it
|
|
3
|
+
* has none. Only the first `\caption{}` counts; `remark-notation.mjs` fails a
|
|
4
|
+
* page that gives one equation two, or an empty one.
|
|
5
|
+
*/
|
|
6
|
+
export function equationCaption(latex: any): string | null;
|
|
1
7
|
/** `{ lesson?, key }` when `raw` is an equation reference, else `undefined`. */
|
|
2
8
|
export function parseEquationRef(raw: any): {
|
|
3
9
|
lesson: string;
|
|
@@ -6,27 +12,31 @@ export function parseEquationRef(raw: any): {
|
|
|
6
12
|
key: string;
|
|
7
13
|
lesson?: undefined;
|
|
8
14
|
} | undefined;
|
|
9
|
-
/**
|
|
15
|
+
/**
|
|
16
|
+
* Remove every `\label{eq:…}` and `\caption{…}` (and the whitespace they
|
|
17
|
+
* leave) from a math source: both are identity markers, not math KaTeX parses.
|
|
18
|
+
*/
|
|
10
19
|
export function stripEquationLabels(latex: any): string;
|
|
11
20
|
/**
|
|
12
21
|
* Normalise the numbering input to `{ prefix, offset }`.
|
|
13
22
|
*
|
|
14
|
-
* `prefix` is
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
* legacy per-lesson form (`prefix = n`,
|
|
19
|
-
* hand-built trees and unit tests keep
|
|
20
|
-
* `##`-section fallback for
|
|
23
|
+
* `prefix` is everything before the last dot — `‹part›.‹chapter›` under the
|
|
24
|
+
* default `numbering.floats: "chapter"`, the part's ordinal under `"part"`.
|
|
25
|
+
* `offset` is how many items of the kind precede the lesson in that scope: 0
|
|
26
|
+
* for a chapter, the earlier chapters' count for a part, so `n` runs on
|
|
27
|
+
* across it. A bare number is the legacy per-lesson form (`prefix = n`,
|
|
28
|
+
* `offset = 0`) and is still accepted so hand-built trees and unit tests keep
|
|
29
|
+
* working. `undefined`/`null`/`0`/`''` ⇒ the `##`-section fallback for
|
|
30
|
+
* non-lesson pages.
|
|
21
31
|
*/
|
|
22
32
|
export function resolveNumbering(input: any): {
|
|
23
33
|
prefix: any;
|
|
24
34
|
offset: number;
|
|
25
35
|
};
|
|
26
36
|
/**
|
|
27
|
-
* The visible number for one display equation. With a `prefix`
|
|
28
|
-
*
|
|
29
|
-
*
|
|
37
|
+
* The visible number for one display equation. With a `prefix` the scheme is
|
|
38
|
+
* `‹prefix›.‹offset + indexInLesson›` and `##` headings are ignored; without
|
|
39
|
+
* it, the legacy `##`-section scheme.
|
|
30
40
|
*/
|
|
31
41
|
export function displayEquationNumber({ prefix, offset, section, indexInSection, indexInLesson, }: {
|
|
32
42
|
prefix: any;
|
|
@@ -35,10 +45,16 @@ export function displayEquationNumber({ prefix, offset, section, indexInSection,
|
|
|
35
45
|
indexInSection: any;
|
|
36
46
|
indexInLesson: any;
|
|
37
47
|
}): string;
|
|
38
|
-
/**
|
|
48
|
+
/**
|
|
49
|
+
* A display equation's anchor id: `eq-<key>` when it carries a
|
|
50
|
+
* `\label{eq:…}`, else the positional form, e.g. `2.4` → `eq-2-4`.
|
|
51
|
+
*
|
|
52
|
+
* @param {{ key?: string, number?: string | number }} equation - `number` is
|
|
53
|
+
* read only when there is no `key`, so a keyed caller may omit it.
|
|
54
|
+
*/
|
|
39
55
|
export function equationAnchorId({ key, number }: {
|
|
40
|
-
key
|
|
41
|
-
number
|
|
56
|
+
key?: string;
|
|
57
|
+
number?: string | number;
|
|
42
58
|
}): string;
|
|
43
59
|
export function stripFencedCode(markdown: any): string;
|
|
44
60
|
/**
|
|
@@ -53,20 +69,20 @@ export function stripFencedCode(markdown: any): string;
|
|
|
53
69
|
* component while still finding `<Table>`/`<Figure>` tags at the top level.
|
|
54
70
|
*
|
|
55
71
|
* `transparent`, when given, removes only those components' opening/closing
|
|
56
|
-
* tags and exposes their body to the later blanking pass.
|
|
57
|
-
*
|
|
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
|
|
72
|
+
* tags and exposes their body to the later blanking pass. The
|
|
73
|
+
* {@link TRANSPARENT_COMPONENTS} are the intentional uses. Section headings in
|
|
60
74
|
* a transparent body do not change the surrounding page's section counter.
|
|
61
75
|
*/
|
|
62
76
|
export function stripJsxComponents(markdown: any, keep: any, transparent?: any[]): string;
|
|
63
77
|
/**
|
|
64
78
|
* Every display equation in a Markdown body, keyed or not, in appearance order
|
|
65
|
-
* with its assigned number
|
|
66
|
-
*
|
|
67
|
-
*
|
|
68
|
-
*
|
|
69
|
-
*
|
|
79
|
+
* with its assigned number and its `\caption{}` short caption (`short`, or
|
|
80
|
+
* `null`). `$$…$$` blocks only. With `numbering`
|
|
81
|
+
* (`{ prefix, offset }`, the lesson's float-number prefix and the count of
|
|
82
|
+
* display equations before it in that scope) the number is
|
|
83
|
+
* `‹prefix›.‹offset + n›`, `n` sequential over every display block in the
|
|
84
|
+
* lesson; without it, `##` headings advance a section counter and the number
|
|
85
|
+
* is `‹section›.‹indexInSection›`.
|
|
70
86
|
* Either way every display block advances the index, so a keyed equation's
|
|
71
87
|
* number reflects its position among *all* display equations, not only the
|
|
72
88
|
* keyed ones.
|
|
@@ -74,6 +90,7 @@ export function stripJsxComponents(markdown: any, keep: any, transparent?: any[]
|
|
|
74
90
|
export function scanDisplayEquations(markdown: any, numbering: any): {
|
|
75
91
|
key: string | null | undefined;
|
|
76
92
|
number: string;
|
|
93
|
+
short: string | null | undefined;
|
|
77
94
|
section: number;
|
|
78
95
|
indexInSection: number;
|
|
79
96
|
}[];
|
|
@@ -81,6 +98,7 @@ export function scanDisplayEquations(markdown: any, numbering: any): {
|
|
|
81
98
|
export function scanEquationLabels(markdown: any, numbering: any): {
|
|
82
99
|
key: string | null | undefined;
|
|
83
100
|
number: string;
|
|
101
|
+
short: string | null | undefined;
|
|
84
102
|
section: number;
|
|
85
103
|
indexInSection: number;
|
|
86
104
|
}[];
|
|
@@ -109,23 +127,24 @@ export function collectEquationLabels(tree: any, numbering: any): any[];
|
|
|
109
127
|
* Every display equation (`$$…$$`) in a lesson is numbered and given an `id`
|
|
110
128
|
* from appearance order. When the caller supplies `numbering`
|
|
111
129
|
* (`{ prefix, offset }` — see `resolveNumbering`), the number is
|
|
112
|
-
* `‹
|
|
113
|
-
*
|
|
114
|
-
*
|
|
115
|
-
*
|
|
116
|
-
*
|
|
117
|
-
* part
|
|
118
|
-
*
|
|
119
|
-
*
|
|
120
|
-
* trees and non-lesson
|
|
130
|
+
* `‹prefix›.‹offset + n›`: `n` counts every display equation in the lesson,
|
|
131
|
+
* and the course's `numbering.floats` scope decides the rest — `chapter` (the
|
|
132
|
+
* default) makes the prefix `‹part›.‹chapter›` with no offset, so `(2.2.4)` is
|
|
133
|
+
* the fourth display equation of part 2's second chapter; `part` makes it the
|
|
134
|
+
* part's ordinal alone, with `offset` carrying the count from earlier lessons
|
|
135
|
+
* of the part so the sequence runs on across it. `##` and `###` headings never
|
|
136
|
+
* affect it. See `docs/authoring/structure.md` for the level names. With no
|
|
137
|
+
* `numbering` the number falls back to the `##`-section scheme
|
|
138
|
+
* `‹section›.‹indexInSection›` (used by hand-built test trees and non-lesson
|
|
139
|
+
* pages). An author additionally
|
|
121
140
|
* marks an equation with a trailing `\label{eq:<key>}` inside its `$$…$$`
|
|
122
141
|
* block (inside math, so the `:` is safe and the form matches amsmath) when
|
|
123
142
|
* they want a *stable* anchor that survives edits and cross-page references:
|
|
124
143
|
*
|
|
125
|
-
* [[eq-discount-factor-def]] same page → "(2.4)" linked to #eq-…
|
|
144
|
+
* [[eq-discount-factor-def]] same page → "(2.2.4)" linked to #eq-…
|
|
126
145
|
* [[foundations/present-value#eq-pv]] cross page → number from the manifest
|
|
127
146
|
*
|
|
128
|
-
* An unkeyed equation still gets a positional anchor (`#eq-2-4`) so it can be
|
|
147
|
+
* An unkeyed equation still gets a positional anchor (`#eq-2-2-4`) so it can be
|
|
129
148
|
* deep-linked, but only a keyed equation is referenceable by name.
|
|
130
149
|
*
|
|
131
150
|
* `scanEquationLabels` (raw Markdown) and `collectEquationLabels` (mdast tree)
|
|
@@ -138,9 +157,25 @@ export function collectEquationLabels(tree: any, numbering: any): any[];
|
|
|
138
157
|
export const EQ_KEY_PATTERN: RegExp;
|
|
139
158
|
/** A `\label{eq:<key>}` anywhere in a math source. Global; `key` in group 1. */
|
|
140
159
|
export const EQ_LABEL_PATTERN: RegExp;
|
|
160
|
+
/**
|
|
161
|
+
* A `\caption{<short caption>}` anywhere in a math source — the equation's
|
|
162
|
+
* short caption, plain text with no nested braces. Global; the text in group
|
|
163
|
+
* 1. An equation takes only a short caption: it names the equation in the
|
|
164
|
+
* `[[eq-…]]` preview card and in the "Copy LaTeX — ‹caption›" tooltip, and is
|
|
165
|
+
* stripped before KaTeX exactly like `\label{}`.
|
|
166
|
+
*/
|
|
167
|
+
export const EQ_CAPTION_PATTERN: RegExp;
|
|
141
168
|
/**
|
|
142
169
|
* A `[[ … ]]` payload that targets an equation: `eq-<key>` on the current page,
|
|
143
170
|
* or `<lesson-slug>#eq-<key>` across pages. `lesson` (group 1) is a `/`-slug.
|
|
144
171
|
* The `eq-` prefix is reserved — a notation key may not start with it.
|
|
145
172
|
*/
|
|
146
173
|
export const EQ_REF_PATTERN: RegExp;
|
|
174
|
+
/**
|
|
175
|
+
* The components whose bodies stay in the lesson's reading order: `<Memo>`,
|
|
176
|
+
* `<ShortNote>` and `<Fold>`. Unlike a worked-example/presentation wrapper,
|
|
177
|
+
* their bodies are details or folded stretches of the lesson itself, so a
|
|
178
|
+
* numbered equation or float in one stays in the official sequence. Every
|
|
179
|
+
* scan and tree walk that numbers equations or floats reads this list.
|
|
180
|
+
*/
|
|
181
|
+
export const TRANSPARENT_COMPONENTS: readonly string[];
|