explico 0.11.0 → 0.12.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 +189 -1
- package/dist/compiler/astro.d.ts +7 -1
- package/dist/compiler/astro.js +5 -2
- package/dist/compiler/cli.d.ts +1 -0
- package/dist/compiler/collections.d.ts +25 -2
- package/dist/compiler/collections.js +37 -4
- package/dist/compiler/exports/standalone-html.d.ts +1 -0
- package/dist/compiler/manifest.d.ts +30 -4
- package/dist/compiler/manifest.js +49 -10
- package/dist/compiler/resolution-report.d.ts +1 -0
- package/dist/reference/base-operators.d.mts +6 -0
- package/dist/reference/base-operators.mjs +6 -8
- package/dist/reference/figure-symbols.d.ts +32 -0
- package/dist/reference/figure-symbols.js +55 -0
- package/dist/reference/gate-non-chapter-pages.d.ts +39 -0
- package/dist/reference/gate-non-chapter-pages.js +156 -0
- package/dist/reference/gate-part-pages.d.ts +3 -1
- package/dist/reference/gate-part-pages.js +2 -1
- package/dist/reference/lab-katex-validate.d.ts +43 -0
- package/dist/reference/lab-katex-validate.js +96 -0
- package/dist/reference/lint-ignore.js +1 -1
- package/dist/reference/page-notation.d.ts +5 -3
- package/dist/reference/page-notation.js +8 -8
- package/dist/reference/raw-floats.mjs +37 -13
- package/dist/reference/registry.js +11 -1
- package/dist/reference/remark-notation.mjs +195 -7
- package/dist/reference/render-lab-math.d.ts +11 -2
- package/dist/reference/render-lab-math.js +2 -2
- package/dist/rules/catalog.js +33 -3
- package/dist/rules/rules.schema.json +22 -4
- package/docs/VERSIONING.md +3 -1
- package/docs/authoring/README.md +2 -1
- package/docs/authoring/components.md +4 -4
- package/docs/authoring/migrating.md +271 -0
- package/docs/authoring/notation.md +4 -1
- package/docs/authoring/structure-rules.md +75 -5
- package/docs/authoring/structure.md +14 -0
- package/docs/authoring/visualizations.md +26 -10
- package/package.json +1 -1
- package/src/compiler/astro.ts +17 -6
- package/src/compiler/collections.ts +67 -9
- package/src/compiler/manifest.ts +89 -13
- package/src/components/CourseContents.astro +6 -0
- package/src/components/PartOverview.astro +6 -0
- package/src/components/abbreviation/AbbreviationGlossary.astro +6 -2
- package/src/components/disclosure/print-as-shown.ts +42 -6
- package/src/components/figure/Figure.astro +6 -1
- package/src/components/figure/figure-state.ts +26 -17
- package/src/components/starlight/LessonFooter.astro +27 -7
- package/src/components/table/Table.astro +1 -1
- package/src/reference/base-operators.mjs +6 -8
- package/src/reference/figure-symbols.ts +117 -0
- package/src/reference/gate-non-chapter-pages.ts +241 -0
- package/src/reference/gate-part-pages.ts +1 -1
- package/src/reference/lab-katex-validate.ts +156 -0
- package/src/reference/lint-ignore.ts +4 -1
- package/src/reference/page-notation.ts +14 -11
- package/src/reference/raw-floats.mjs +37 -13
- package/src/reference/registry.ts +16 -1
- package/src/reference/remark-notation.mjs +195 -7
- package/src/reference/render-lab-math.ts +12 -1
- package/src/rules/catalog.ts +37 -3
- package/src/styles/global.css +57 -38
|
@@ -106,6 +106,13 @@ function buildNotationRegistry(rawInput) {
|
|
|
106
106
|
);
|
|
107
107
|
}
|
|
108
108
|
detectCycles(definitions, edgeIndex, diagnostics);
|
|
109
|
+
const localIdsByLesson = /* @__PURE__ */ new Map();
|
|
110
|
+
for (const candidate of definitions.values()) {
|
|
111
|
+
if (candidate.kind !== "local") continue;
|
|
112
|
+
const ids = localIdsByLesson.get(candidate.lesson.lessonId) ?? [];
|
|
113
|
+
ids.push(candidate.id);
|
|
114
|
+
localIdsByLesson.set(candidate.lesson.lessonId, ids);
|
|
115
|
+
}
|
|
109
116
|
const bundles = [];
|
|
110
117
|
const backlinks = [];
|
|
111
118
|
const reachableDefinitions = /* @__PURE__ */ new Set();
|
|
@@ -114,7 +121,10 @@ function buildNotationRegistry(rawInput) {
|
|
|
114
121
|
const directIds = new Set(
|
|
115
122
|
references.map((reference) => reference.definitionId)
|
|
116
123
|
);
|
|
117
|
-
const definitionIds = transitiveClosure(
|
|
124
|
+
const definitionIds = transitiveClosure(
|
|
125
|
+
/* @__PURE__ */ new Set([...directIds, ...localIdsByLesson.get(lesson.lessonId) ?? []]),
|
|
126
|
+
edgeIndex
|
|
127
|
+
);
|
|
118
128
|
for (const id of definitionIds) reachableDefinitions.add(id);
|
|
119
129
|
const bindingByKey = /* @__PURE__ */ new Map();
|
|
120
130
|
for (const reference of references) {
|
|
@@ -46,6 +46,7 @@ import {
|
|
|
46
46
|
equationAnchorId,
|
|
47
47
|
parseEquationRef,
|
|
48
48
|
stripEquationLabels,
|
|
49
|
+
TRANSPARENT_COMPONENTS,
|
|
49
50
|
} from './equations.mjs';
|
|
50
51
|
import {
|
|
51
52
|
collectKeyedJsxFloats,
|
|
@@ -184,6 +185,25 @@ function lessonIdFrom(file) {
|
|
|
184
185
|
return typeof lessonId === 'string' ? lessonId : undefined;
|
|
185
186
|
}
|
|
186
187
|
|
|
188
|
+
/** True for any page under `content/docs/`; a lesson is one of them. */
|
|
189
|
+
function isDocsPageFile(file) {
|
|
190
|
+
return [file?.path, file?.history?.[0], file?.data?.file].some(
|
|
191
|
+
(value) => typeof value === 'string' && LESSON_DOC_PATH.test(value),
|
|
192
|
+
);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* The blocks a chapter numbers (`makeFloatCtx` below, and the knowledge
|
|
197
|
+
* check). Rule S10 keeps each off a page that is not a chapter.
|
|
198
|
+
*/
|
|
199
|
+
const NUMBERED_BLOCK_NAMES = new Set([
|
|
200
|
+
'Table',
|
|
201
|
+
'Figure',
|
|
202
|
+
'Diagram',
|
|
203
|
+
'CompactExamples',
|
|
204
|
+
'AssessmentSet',
|
|
205
|
+
]);
|
|
206
|
+
|
|
187
207
|
const ANY_TERM_KEY = /\[\[\s*([^\][]+?)\s*\]\]/g;
|
|
188
208
|
const ANY_EXPLAIN_KEY = /\\explain\s*\{([^{}]+)\}/g;
|
|
189
209
|
|
|
@@ -467,6 +487,130 @@ function injectFloatNumberAttributes(node, float, prefix) {
|
|
|
467
487
|
});
|
|
468
488
|
}
|
|
469
489
|
|
|
490
|
+
/**
|
|
491
|
+
* The URL path Starlight serves a docs page at: `motion/index.mdx` is
|
|
492
|
+
* `/motion/`, `motion/speed.mdx` is `/motion/speed/`, `index.mdx` is `/`.
|
|
493
|
+
*/
|
|
494
|
+
function pageUrlFrom(file) {
|
|
495
|
+
for (const value of [file?.path, file?.history?.[0], file?.data?.file]) {
|
|
496
|
+
if (typeof value !== 'string') continue;
|
|
497
|
+
const match = value.match(LESSON_DOC_PATH);
|
|
498
|
+
if (!match) continue;
|
|
499
|
+
const route = match[1].replace(/\\/g, '/').replace(/(?:^|\/)index$/, '');
|
|
500
|
+
return route ? `/${route}/` : '/';
|
|
501
|
+
}
|
|
502
|
+
return undefined;
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
/**
|
|
506
|
+
* The chapter slug a link's `href` names — `motion/speed` for
|
|
507
|
+
* `/motion/speed/#sec-x`, for `../speed/` from a sibling page, and for
|
|
508
|
+
* `/base/motion/speed/` under the course's `base` — or `undefined` for a
|
|
509
|
+
* same-page anchor, another site, or a scheme such as `mailto:`.
|
|
510
|
+
*/
|
|
511
|
+
function linkedSlug(href, pageUrl, base) {
|
|
512
|
+
if (typeof href !== 'string' || href === '' || href.startsWith('#')) return undefined;
|
|
513
|
+
if (href.startsWith('//') || /^[a-z][a-z0-9+.-]*:/i.test(href)) return undefined;
|
|
514
|
+
let url;
|
|
515
|
+
try {
|
|
516
|
+
url = new URL(href, `https://course.invalid${pageUrl ?? '/'}`);
|
|
517
|
+
} catch {
|
|
518
|
+
return undefined;
|
|
519
|
+
}
|
|
520
|
+
let path;
|
|
521
|
+
try {
|
|
522
|
+
path = decodeURIComponent(url.pathname);
|
|
523
|
+
} catch {
|
|
524
|
+
return undefined;
|
|
525
|
+
}
|
|
526
|
+
const prefix = base && base !== '/' ? base.replace(/\/+$/, '') : '';
|
|
527
|
+
if (prefix && (path === prefix || path.startsWith(`${prefix}/`))) {
|
|
528
|
+
path = path.slice(prefix.length);
|
|
529
|
+
}
|
|
530
|
+
return path.replace(/^\/+|\/+$/g, '');
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
/**
|
|
534
|
+
* A link the author wrote to another chapter shows that chapter's full title
|
|
535
|
+
* as its native tooltip (`title`): prose names a chapter any way it likes —
|
|
536
|
+
* "the previous chapter", "Chapter 1.2", "this derivation" — and a reader can
|
|
537
|
+
* see where the link goes before following it. A Markdown link and a JSX
|
|
538
|
+
* `<a href>` both get it; a link with a title of its own, or one to the page
|
|
539
|
+
* it is on, keeps what it has.
|
|
540
|
+
*/
|
|
541
|
+
function titleChapterLinks(tree, file, titleBySlug, base) {
|
|
542
|
+
if (titleBySlug.size === 0) return;
|
|
543
|
+
const pageUrl = pageUrlFrom(file);
|
|
544
|
+
const here = pageUrl?.replace(/^\/+|\/+$/g, '');
|
|
545
|
+
const titleOf = (href) => {
|
|
546
|
+
const slug = linkedSlug(href, pageUrl, base);
|
|
547
|
+
return slug !== undefined && slug !== here ? titleBySlug.get(slug) : undefined;
|
|
548
|
+
};
|
|
549
|
+
const walk = (node) => {
|
|
550
|
+
if (!node || typeof node !== 'object') return;
|
|
551
|
+
if (node.type === 'link' && !node.title) {
|
|
552
|
+
const title = titleOf(node.url);
|
|
553
|
+
if (title) node.title = title;
|
|
554
|
+
} else if (
|
|
555
|
+
(node.type === 'mdxJsxTextElement' || node.type === 'mdxJsxFlowElement') &&
|
|
556
|
+
node.name === 'a'
|
|
557
|
+
) {
|
|
558
|
+
const attributes = node.attributes ?? [];
|
|
559
|
+
const named = (name) =>
|
|
560
|
+
attributes.find((attribute) => attribute?.type === 'mdxJsxAttribute' && attribute.name === name);
|
|
561
|
+
const title = named('title') ? undefined : titleOf(named('href')?.value);
|
|
562
|
+
if (title) {
|
|
563
|
+
node.attributes = [...attributes, { type: 'mdxJsxAttribute', name: 'title', value: title }];
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
if (Array.isArray(node.children)) node.children.forEach(walk);
|
|
567
|
+
};
|
|
568
|
+
walk(tree);
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
/**
|
|
572
|
+
* The innermost component around `target` that numbering does not see
|
|
573
|
+
* through — the reason a float in it has no number — or `undefined`.
|
|
574
|
+
*/
|
|
575
|
+
function blockingComponent(tree, target) {
|
|
576
|
+
const search = (node, blocking) => {
|
|
577
|
+
if (node === target) return blocking;
|
|
578
|
+
if (!node || typeof node !== 'object' || !Array.isArray(node.children)) return undefined;
|
|
579
|
+
const here =
|
|
580
|
+
node.type === 'mdxJsxFlowElement' &&
|
|
581
|
+
node.name &&
|
|
582
|
+
!TRANSPARENT_COMPONENTS.includes(node.name)
|
|
583
|
+
? node.name
|
|
584
|
+
: blocking;
|
|
585
|
+
for (const child of node.children) {
|
|
586
|
+
const found = search(child, here);
|
|
587
|
+
if (found !== undefined) return found;
|
|
588
|
+
}
|
|
589
|
+
return undefined;
|
|
590
|
+
};
|
|
591
|
+
return search(tree, undefined);
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
/**
|
|
595
|
+
* Why a `<Table>`/`<Figure>` on a lesson page got no number: it sits in a
|
|
596
|
+
* component other than the `TRANSPARENT_COMPONENTS`, where a float is
|
|
597
|
+
* illustrative, as an equation there is (`keyed-jsx-floats.mjs`).
|
|
598
|
+
*/
|
|
599
|
+
function unnumberedFloatMessage(tagName, component) {
|
|
600
|
+
const where = component ? `<${component}>` : 'another component';
|
|
601
|
+
const noun = tagName === 'Table' ? 'table' : 'figure';
|
|
602
|
+
const instead =
|
|
603
|
+
tagName === 'Table'
|
|
604
|
+
? 'write it as a plain Markdown table (rule S3 does not flag one there)'
|
|
605
|
+
: 'show its content without <Figure>';
|
|
606
|
+
return (
|
|
607
|
+
`<${tagName}> inside ${where} gets no number: a lesson numbers a ${noun} ` +
|
|
608
|
+
`at its top level or in a <Memo>, <ShortNote> or <Fold>. A ${noun} in a ` +
|
|
609
|
+
`worked example or another component is illustrative, like an equation ` +
|
|
610
|
+
`there: ${instead}, or move the <${tagName}> out of ${where}.`
|
|
611
|
+
);
|
|
612
|
+
}
|
|
613
|
+
|
|
470
614
|
/** Resolve a `[[eq-key]]` / `[[slug#eq-key]]` reference to `{ number, href }`. */
|
|
471
615
|
function resolveEquationRef(eqRef, eqCtx, file, node) {
|
|
472
616
|
if (eqRef.lesson) {
|
|
@@ -1287,6 +1431,9 @@ export default function remarkNotation(options = {}) {
|
|
|
1287
1431
|
// The manifest supplies every numbering input at once; an explicit option
|
|
1288
1432
|
// still overrides it, so existing per-field wiring keeps working.
|
|
1289
1433
|
const numbering = { ...numberingFromManifest(options.manifest), ...options };
|
|
1434
|
+
const chapterTitleBySlug = new Map(
|
|
1435
|
+
(options.manifest?.lessons ?? []).map((lesson) => [lesson.slug, lesson.title]),
|
|
1436
|
+
);
|
|
1290
1437
|
// Every lesson's headings by anchor, built once: `[[lesson#sec-…]]` reads
|
|
1291
1438
|
// another page's, `[[sec-…]]` the page's own.
|
|
1292
1439
|
const sectionSettings = { show: true, depth: 4, ...numbering.sectionNumbering };
|
|
@@ -1310,6 +1457,9 @@ export default function remarkNotation(options = {}) {
|
|
|
1310
1457
|
const suspendedOperators = suspendedOperatorsFromFile(file);
|
|
1311
1458
|
const lessonId = lessonIdFrom(file);
|
|
1312
1459
|
const lessonSlug = lessonId ? lessonId.replace(/\./g, '/') : undefined;
|
|
1460
|
+
// The landing page, a part's overview page (rule S10): its math binds
|
|
1461
|
+
// like a chapter's, but it numbers nothing.
|
|
1462
|
+
const nonChapterPage = !lessonId && isDocsPageFile(file);
|
|
1313
1463
|
// `{ prefix, offset }` per kind of float — see the `numbering` option.
|
|
1314
1464
|
// Missing ⇒ the legacy `##`-section numbering.
|
|
1315
1465
|
const equationNumbering = pageNumbering(numbering, lessonSlug, 'equations');
|
|
@@ -1329,6 +1479,13 @@ export default function remarkNotation(options = {}) {
|
|
|
1329
1479
|
const equationLabels = collectEquationLabels(tree, equationNumbering);
|
|
1330
1480
|
const pageLabels = new Map();
|
|
1331
1481
|
for (const label of equationLabels) {
|
|
1482
|
+
if (nonChapterPage) {
|
|
1483
|
+
fail(
|
|
1484
|
+
file,
|
|
1485
|
+
`\\label{eq:${label.key}} on a page that is not a chapter (rule S10): only a chapter numbers an equation. Drop the label to show the equation unnumbered, or link the chapter's own with [[<chapter>#eq-<key>]].`,
|
|
1486
|
+
label.node,
|
|
1487
|
+
);
|
|
1488
|
+
}
|
|
1332
1489
|
if (pageLabels.has(label.key)) {
|
|
1333
1490
|
fail(
|
|
1334
1491
|
file,
|
|
@@ -1568,14 +1725,19 @@ export default function remarkNotation(options = {}) {
|
|
|
1568
1725
|
|
|
1569
1726
|
inspectExplainCalls(value, resolve, file, node, references);
|
|
1570
1727
|
|
|
1571
|
-
const bindingScope =
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1728
|
+
const bindingScope =
|
|
1729
|
+
lessonId || nonChapterPage
|
|
1730
|
+
? mathScope
|
|
1731
|
+
: registryFile
|
|
1732
|
+
? registryMathDefinitions
|
|
1733
|
+
: undefined;
|
|
1576
1734
|
|
|
1577
1735
|
if (bindingScope) {
|
|
1578
|
-
const context = lessonId
|
|
1736
|
+
const context = lessonId
|
|
1737
|
+
? 'lesson math'
|
|
1738
|
+
: nonChapterPage
|
|
1739
|
+
? 'page math'
|
|
1740
|
+
: 'notation definition math';
|
|
1579
1741
|
|
|
1580
1742
|
let result;
|
|
1581
1743
|
try {
|
|
@@ -1608,7 +1770,9 @@ export default function remarkNotation(options = {}) {
|
|
|
1608
1770
|
.join(', ');
|
|
1609
1771
|
fail(
|
|
1610
1772
|
file,
|
|
1611
|
-
|
|
1773
|
+
nonChapterPage
|
|
1774
|
+
? `Unresolved notation in ${context}: ${unresolved}. Introduce the symbol with [[key]], or wrap it in \\explain{key}{latex}: a page that is not a chapter recalls shared notation and defines none (rule S10).`
|
|
1775
|
+
: `Unresolved notation in ${context}: ${unresolved}. Introduce the symbol with [[key]], define it in notation.local, or wrap it in \\explain{key}{latex}.`,
|
|
1612
1776
|
node,
|
|
1613
1777
|
);
|
|
1614
1778
|
}
|
|
@@ -1668,6 +1832,27 @@ export default function remarkNotation(options = {}) {
|
|
|
1668
1832
|
injectFloatNumberAttributes(child, diagramByNode.get(child), 'dia');
|
|
1669
1833
|
} else if (child?.type === 'mdxJsxFlowElement' && exampleByNode.has(child)) {
|
|
1670
1834
|
injectFloatNumberAttributes(child, exampleByNode.get(child), 'ex');
|
|
1835
|
+
} else if (
|
|
1836
|
+
nonChapterPage &&
|
|
1837
|
+
child?.type === 'mdxJsxFlowElement' &&
|
|
1838
|
+
NUMBERED_BLOCK_NAMES.has(child.name)
|
|
1839
|
+
) {
|
|
1840
|
+
// `validate:content` reports this first (gate-non-chapter-pages.ts);
|
|
1841
|
+
// this is the same message for a build that skipped it.
|
|
1842
|
+
fail(
|
|
1843
|
+
file,
|
|
1844
|
+
`<${child.name}> on a page that is not a chapter (rule S10): only a chapter numbers it. Move it into the chapter it belongs to, and link it from here: the link shows it in its card.`,
|
|
1845
|
+
child,
|
|
1846
|
+
);
|
|
1847
|
+
} else if (
|
|
1848
|
+
lessonId &&
|
|
1849
|
+
child?.type === 'mdxJsxFlowElement' &&
|
|
1850
|
+
(child.name === 'Table' || child.name === 'Figure')
|
|
1851
|
+
) {
|
|
1852
|
+
// Numbered everywhere a lesson numbers one, so this one is nested
|
|
1853
|
+
// in a component that is not transparent. Say so here, at its
|
|
1854
|
+
// line, before the component fails on its missing number.
|
|
1855
|
+
fail(file, unnumberedFloatMessage(child.name, blockingComponent(tree, child)), child);
|
|
1671
1856
|
}
|
|
1672
1857
|
}
|
|
1673
1858
|
};
|
|
@@ -1727,6 +1912,9 @@ export default function remarkNotation(options = {}) {
|
|
|
1727
1912
|
}
|
|
1728
1913
|
}
|
|
1729
1914
|
|
|
1915
|
+
// Before `visit`, which writes the `[[…]]` reference links: each of
|
|
1916
|
+
// those opens a card of its own.
|
|
1917
|
+
titleChapterLinks(tree, file, chapterTitleBySlug, options.base);
|
|
1730
1918
|
visit(tree);
|
|
1731
1919
|
|
|
1732
1920
|
file.data.notationReferences = references;
|
|
@@ -5,12 +5,21 @@ interface LabMathScopeDefinition {
|
|
|
5
5
|
readonly key: string;
|
|
6
6
|
readonly notation: string;
|
|
7
7
|
}
|
|
8
|
+
interface RenderLabMathOptions {
|
|
9
|
+
/**
|
|
10
|
+
* `false` renders the template as inline math — a `.katex` with no
|
|
11
|
+
* `.katex-display` — for a formula that sits in a line: a legend entry,
|
|
12
|
+
* an axis title, a control label. It never scrolls (`global.css`
|
|
13
|
+
* `.lab-math`). Default `true`, a display equation.
|
|
14
|
+
*/
|
|
15
|
+
readonly display?: boolean;
|
|
16
|
+
}
|
|
8
17
|
/**
|
|
9
18
|
* Compile a developer-authored lab math template the same way lesson `$$`
|
|
10
19
|
* math is compiled: every identifier must resolve to a notation key in the
|
|
11
20
|
* page's scope or this throws. Slots are set aside during binding and restored
|
|
12
21
|
* as inert `data-lab-slot` spans the interactive island rewrites at runtime.
|
|
13
22
|
*/
|
|
14
|
-
declare function renderLabMath(template: string, scope: readonly LabMathScopeDefinition[]): RenderedLabMath;
|
|
23
|
+
declare function renderLabMath(template: string, scope: readonly LabMathScopeDefinition[], { display }?: RenderLabMathOptions): RenderedLabMath;
|
|
15
24
|
|
|
16
|
-
export { type LabMathScopeDefinition, RenderedLabMath, renderLabMath };
|
|
25
|
+
export { type LabMathScopeDefinition, type RenderLabMathOptions, RenderedLabMath, renderLabMath };
|
|
@@ -4,7 +4,7 @@ import { createNotationKatexOptions } from "./katex-options.mjs";
|
|
|
4
4
|
import { slotPlaceholder } from "./lab-math-slot.js";
|
|
5
5
|
const SLOT_PATTERN = /\\slot\{([a-z][a-z0-9]*)\}\{((?:[^{}]|\{[^{}]*\})*)\}/g;
|
|
6
6
|
const sentinelFor = (index) => `909909${index}.909909`;
|
|
7
|
-
function renderLabMath(template, scope) {
|
|
7
|
+
function renderLabMath(template, scope, { display = true } = {}) {
|
|
8
8
|
const slots = [];
|
|
9
9
|
const forBinding = template.replace(
|
|
10
10
|
SLOT_PATTERN,
|
|
@@ -50,7 +50,7 @@ function renderLabMath(template, scope) {
|
|
|
50
50
|
});
|
|
51
51
|
const html = katex.renderToString(
|
|
52
52
|
bound,
|
|
53
|
-
createNotationKatexOptions({ displayMode:
|
|
53
|
+
createNotationKatexOptions({ displayMode: display })
|
|
54
54
|
);
|
|
55
55
|
return {
|
|
56
56
|
html,
|
package/dist/rules/catalog.js
CHANGED
|
@@ -387,8 +387,14 @@ const RULES = [
|
|
|
387
387
|
title: "Interactive figures: a hydrated island and the `renderLabMath` seam for equations.",
|
|
388
388
|
elements: ["plot"],
|
|
389
389
|
doc: "visualizations.md#v3",
|
|
390
|
-
summary: "An interactive figure is an island under `content/labs/` inside a `<Figure>`, renders its equations through `renderLabMath
|
|
391
|
-
checks: [
|
|
390
|
+
summary: "An interactive figure is an island under `content/labs/` inside a `<Figure>`, renders its equations through `renderLabMath` (`{ display: false }` for a legend, an axis title or a label; a `{ math }` run for the caption clause) and never KaTeX directly, and labels its controls; the figure\u2019s caption is its name, so the lab writes no accessible name or text alternative of its own.",
|
|
391
|
+
checks: [
|
|
392
|
+
check("lab-katex-direct", "error", false, {
|
|
393
|
+
module: "src/reference/lab-katex-validate.ts",
|
|
394
|
+
fn: "validateLabKatexImports"
|
|
395
|
+
})
|
|
396
|
+
],
|
|
397
|
+
unchecked: "that a lab labels its controls and writes no name of its own, and math a lab draws without importing `katex` \u2014 the build\u2019s rendered check (`figure-symbols.ts`) covers the page it renders."
|
|
392
398
|
},
|
|
393
399
|
{
|
|
394
400
|
kind: "rule",
|
|
@@ -555,7 +561,7 @@ const RULES = [
|
|
|
555
561
|
title: 'A table is a `<Table id="\u2026">`, never a raw Markdown or HTML table.',
|
|
556
562
|
elements: ["table"],
|
|
557
563
|
doc: "structure-rules.md#s3",
|
|
558
|
-
summary: 'Wrap every pipe table or `<table>` in `<Table id="\u2026" caption="\u2026">` so it gets a number and `[[tab-\u2026]]` can reach it.',
|
|
564
|
+
summary: 'Wrap every pipe table or `<table>` in `<Table id="\u2026" caption="\u2026">` so it gets a number and `[[tab-\u2026]]` can reach it; a table in a worked example stays plain.',
|
|
559
565
|
checks: [
|
|
560
566
|
check("raw-table-markdown", "warning", false, {
|
|
561
567
|
module: "src/reference/raw-floats-validate.ts",
|
|
@@ -653,6 +659,30 @@ const RULES = [
|
|
|
653
659
|
summary: 'Give each figure, table and diagram a `short` caption that names it: it opens the caption line in bold, and names the float in "Hidden here", a copied link and the "Figures and tables" page. The long `caption` goes on from that name to say what the float shows \u2014 its subject, its marks and symbols, the case shown \u2014 and at most the one thing to notice. The argument drawn from a figure is lesson text after it, and a lab\u2019s instructions are the first line of its `tools`.',
|
|
654
660
|
checks: []
|
|
655
661
|
},
|
|
662
|
+
{
|
|
663
|
+
kind: "rule",
|
|
664
|
+
id: "S10",
|
|
665
|
+
short: "A page that is not a chapter explains its symbols but numbers nothing: it links a chapter\u2019s float instead",
|
|
666
|
+
title: "A page that is not a chapter \u2014 the landing page, a part\u2019s overview page \u2014 explains its symbols like a chapter, but carries no numbered float: it links the chapter\u2019s float instead.",
|
|
667
|
+
elements: ["part-page", "plot", "table", "diagram", "equation", "notation"],
|
|
668
|
+
doc: "structure-rules.md#s10",
|
|
669
|
+
summary: "On `content/docs/index.mdx`, a `content/docs/<part>/index.mdx`, or any page outside a part: `[[key]]`, `\\explain`, math, citations and `[[chapter#tab-key]]` links work and open cards, and the math and prose gates apply. No `<Table>`, `<Figure>`, `<Diagram>`, `<CompactExamples>`, `<AssessmentSet>`, lab, `\\label{eq:\u2026}` or `notation.local`/`notation.suspend`; no `$\u2026$` or `[[\u2026]]` in `hero.title`, `hero.tagline` or a `<PartOverview chain>` step.",
|
|
670
|
+
checks: [
|
|
671
|
+
check("non-chapter-float", "error", true, {
|
|
672
|
+
module: "src/reference/gate-non-chapter-pages.ts",
|
|
673
|
+
fn: "gateNonChapterPages"
|
|
674
|
+
}),
|
|
675
|
+
check("non-chapter-local-notation", "error", false, {
|
|
676
|
+
module: "src/reference/gate-non-chapter-pages.ts",
|
|
677
|
+
fn: "gateNonChapterPages"
|
|
678
|
+
}),
|
|
679
|
+
check("non-chapter-plain-text", "error", false, {
|
|
680
|
+
module: "src/reference/gate-non-chapter-pages.ts",
|
|
681
|
+
fn: "gateNonChapterPages"
|
|
682
|
+
})
|
|
683
|
+
],
|
|
684
|
+
locked: "a float needs a chapter number, and such a page has none \u2014 turning the rule off would not render one."
|
|
685
|
+
},
|
|
656
686
|
// ---- P — prose --------------------------------------------------------------
|
|
657
687
|
{
|
|
658
688
|
kind: "rule",
|
|
@@ -812,6 +812,24 @@
|
|
|
812
812
|
}
|
|
813
813
|
}
|
|
814
814
|
},
|
|
815
|
+
"S10": {
|
|
816
|
+
"title": "S10 — A page that is not a chapter explains its symbols but numbers nothing: it links a chapter’s float instead",
|
|
817
|
+
"description": "On `content/docs/index.mdx`, a `content/docs/<part>/index.mdx`, or any page outside a part: `[[key]]`, `\\explain`, math, citations and `[[chapter#tab-key]]` links work and open cards, and the math and prose gates apply. No `<Table>`, `<Figure>`, `<Diagram>`, `<CompactExamples>`, `<AssessmentSet>`, lab, `\\label{eq:…}` or `notation.local`/`notation.suspend`; no `$…$` or `[[…]]` in `hero.title`, `hero.tagline` or a `<PartOverview chain>` step. Locked — a float needs a chapter number, and such a page has none — turning the rule off would not render one.",
|
|
818
|
+
"x-element": "part-page",
|
|
819
|
+
"x-elements": [
|
|
820
|
+
"part-page",
|
|
821
|
+
"plot",
|
|
822
|
+
"table",
|
|
823
|
+
"diagram",
|
|
824
|
+
"equation",
|
|
825
|
+
"notation"
|
|
826
|
+
],
|
|
827
|
+
"x-enforcement": "locked",
|
|
828
|
+
"x-doc": "docs/authoring/structure-rules.md#s10",
|
|
829
|
+
"type": "object",
|
|
830
|
+
"additionalProperties": false,
|
|
831
|
+
"properties": {}
|
|
832
|
+
},
|
|
815
833
|
"V2": {
|
|
816
834
|
"title": "V2 — Static charts: SVG → D3 → Observable Plot, chosen per figure",
|
|
817
835
|
"description": "For a static chart pick the lightest tool that draws it: inline SVG, then D3, then Observable Plot. State the choice in the figure’s source comment.",
|
|
@@ -834,19 +852,19 @@
|
|
|
834
852
|
},
|
|
835
853
|
"V3": {
|
|
836
854
|
"title": "V3 — Interactive figures: a hydrated island + the `renderLabMath` seam",
|
|
837
|
-
"description": "An interactive figure is an island under `content/labs/` inside a `<Figure>`, renders its equations through `renderLabMath
|
|
855
|
+
"description": "An interactive figure is an island under `content/labs/` inside a `<Figure>`, renders its equations through `renderLabMath` (`{ display: false }` for a legend, an axis title or a label; a `{ math }` run for the caption clause) and never KaTeX directly, and labels its controls; the figure’s caption is its name, so the lab writes no accessible name or text alternative of its own. Not checked by the build: that a lab labels its controls and writes no name of its own, and math a lab draws without importing `katex` — the build’s rendered check (`figure-symbols.ts`) covers the page it renders.",
|
|
838
856
|
"x-element": "plot",
|
|
839
857
|
"x-elements": [
|
|
840
858
|
"plot"
|
|
841
859
|
],
|
|
842
|
-
"x-enforcement": "
|
|
860
|
+
"x-enforcement": "partial",
|
|
843
861
|
"x-doc": "docs/authoring/visualizations.md#v3",
|
|
844
862
|
"type": "object",
|
|
845
863
|
"additionalProperties": false,
|
|
846
864
|
"properties": {
|
|
847
865
|
"enabled": {
|
|
848
866
|
"title": "Enabled",
|
|
849
|
-
"description": "Off: the rule leaves the rules-in-effect page
|
|
867
|
+
"description": "Off: the check does not run, and the rule leaves the rules-in-effect page.",
|
|
850
868
|
"type": "boolean",
|
|
851
869
|
"default": true
|
|
852
870
|
}
|
|
@@ -990,7 +1008,7 @@
|
|
|
990
1008
|
},
|
|
991
1009
|
"S3": {
|
|
992
1010
|
"title": "S3 — A table is a `<Table id=\"…\">`, never a raw Markdown/HTML table",
|
|
993
|
-
"description": "Wrap every pipe table or `<table>` in `<Table id=\"…\" caption=\"…\">` so it gets a number and `[[tab-…]]` can reach it.",
|
|
1011
|
+
"description": "Wrap every pipe table or `<table>` in `<Table id=\"…\" caption=\"…\">` so it gets a number and `[[tab-…]]` can reach it; a table in a worked example stays plain.",
|
|
994
1012
|
"x-element": "table",
|
|
995
1013
|
"x-elements": [
|
|
996
1014
|
"table"
|
package/docs/VERSIONING.md
CHANGED
|
@@ -105,7 +105,9 @@ applies every intervening section in order.
|
|
|
105
105
|
the `migrating.md` section.
|
|
106
106
|
6. Run `pnpm build && pnpm test`, then inspect `pnpm pack --dry-run` and confirm
|
|
107
107
|
that `docs/authoring/migrating.md` and `docs/authoring/agents.md` are listed.
|
|
108
|
-
7. Commit, then `git tag vx.y.z`.
|
|
108
|
+
7. Commit, then tag it annotated: `git tag -a vx.y.z -m "explico x.y.z"`. With
|
|
109
|
+
`push.followTags` on, a plain `git push` sends an annotated tag with its
|
|
110
|
+
commit; a lightweight tag never travels that way.
|
|
109
111
|
|
|
110
112
|
## When you change the engine
|
|
111
113
|
|
package/docs/authoring/README.md
CHANGED
|
@@ -257,7 +257,7 @@ of to the settings file.
|
|
|
257
257
|
| [N22](notation.md#n22) | math, notation | An accent or mark is an operator: universal, explained, or a whole form — settings: `allow` = `[]`, `deny` = `[]` | Script — `accent-explained` (warning), `suspended-operator-unresolved`, `operator-suspension-reason` (warning), `operator-suspended` (info), `operator-shadowed` (info) in [`consistency.ts`](../../src/reference/consistency.ts) `checkAccentExplained`, `checkOperatorSuspension`; [`gate-math.ts`](../../src/reference/gate-math.ts) `resolveInto` |
|
|
258
258
|
| [V1](visualizations.md#v1) | diagram | Graphs and flowcharts use the `Diagram` component | Guide |
|
|
259
259
|
| [V2](visualizations.md#v2) | plot | Static charts: SVG → D3 → Observable Plot, chosen per figure | Guide |
|
|
260
|
-
| [V3](visualizations.md#v3) | plot | Interactive figures: a hydrated island + the `renderLabMath` seam |
|
|
260
|
+
| [V3](visualizations.md#v3) | plot | Interactive figures: a hydrated island + the `renderLabMath` seam | Partial — `lab-katex-direct` in [`lab-katex-validate.ts`](../../src/reference/lab-katex-validate.ts) `validateLabKatexImports`. review: that a lab labels its controls and writes no name of its own, and math a lab draws without importing `katex` — the build’s rendered check (`figure-symbols.ts`) covers the page it renders |
|
|
261
261
|
| [V4](visualizations.md#v4) | plot | Randomised figures expose an adjustable, deterministic seed | Guide |
|
|
262
262
|
| [V5](visualizations.md#v5) | plot | Axis ranges span the first 10 seeds at the current control values — settings: `seeds` = `10` | Guide |
|
|
263
263
|
| [V6](visualizations.md#v6) | plot | Every quantitative axis shows at least three labelled values — settings: `minLabels` = `3` | Guide |
|
|
@@ -273,6 +273,7 @@ of to the settings file.
|
|
|
273
273
|
| [S7](structure-rules.md#s7) | heading | A heading is followed by body content, never by another heading | Script — `heading-adjacent` in [`gate-heading-structure.ts`](../../src/reference/gate-heading-structure.ts) `gateHeadingStructure` |
|
|
274
274
|
| [S8](structure-rules.md#s8) | prose | Prefer `<Memo>` over a Starlight aside when the content needs an anchor, a caption or collapsing | Guide |
|
|
275
275
|
| [S9](structure-rules.md#s9) | plot, table, diagram | A float’s short caption names it; the long caption goes on from that name | Guide |
|
|
276
|
+
| [S10](structure-rules.md#s10) | part-page, plot, table, diagram, equation, notation | A page that is not a chapter explains its symbols but numbers nothing: it links a chapter’s float instead | **Locked** — a float needs a chapter number, and such a page has none — turning the rule off would not render one. Script — `non-chapter-float`, `non-chapter-local-notation`, `non-chapter-plain-text` in [`gate-non-chapter-pages.ts`](../../src/reference/gate-non-chapter-pages.ts) `gateNonChapterPages` |
|
|
276
277
|
| [P1](prose.md#p1) | prose | "or" never introduces a second name for the same thing | Guide |
|
|
277
278
|
| [P2](prose.md#p2) | prose | One term for each concept, repeated — no synonyms for variety | Guide |
|
|
278
279
|
| [P3](prose.md#p3) | prose | One meaning for each term; no technical term in its everyday sense | Guide |
|
|
@@ -141,9 +141,8 @@ on a `<CompactExamples>`, whose heading it is, and is what
|
|
|
141
141
|
[rule S1](structure-rules.md#s1) measures — the long caption is never capped.
|
|
142
142
|
|
|
143
143
|
A figure's, a table's or a diagram's `short` is its name wherever the page
|
|
144
|
-
names it
|
|
145
|
-
|
|
146
|
-
with a period added unless it ends in punctuation of its own — a folded
|
|
144
|
+
names it: the bold title that opens its caption line — "**Sampled flight
|
|
145
|
+
data.** Position, …", with a period added unless it ends in punctuation of its own — a folded
|
|
147
146
|
section's "Hidden here: **Figure 2.1.1** Projectile trajectory explorer, 2
|
|
148
147
|
paragraphs", a `<Fold>`'s bar, and a copied link, which says "Link copied ·
|
|
149
148
|
Figure 2.1.1 · Projectile trajectory explorer" and pastes into a document as
|
|
@@ -168,7 +167,8 @@ is shown.
|
|
|
168
167
|
`<Figure>`, `defaultOpen` (default: the course's `disclosure.figures`) and
|
|
169
168
|
`rememberState` (default `true`) are about the tools; a figure without
|
|
170
169
|
them has no button and no tray, and a `<Diagram>` has neither prop. The
|
|
171
|
-
caption line ends with a live clause a lab writes with `setFigureState
|
|
170
|
+
caption line ends with a live clause a lab writes with `setFigureState`,
|
|
171
|
+
in text and `{ math }` runs, so each symbol in it opens its card
|
|
172
172
|
([visualizations.md § V3](visualizations.md#v3)).
|
|
173
173
|
- **A lab has no title or caption of its own.** The `<Figure>` around it
|
|
174
174
|
names the whole ([visualizations.md § Labs](visualizations.md#v3)).
|