explico 0.4.0 → 0.6.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 +382 -1
- package/README.md +103 -8
- package/dist/compiler/astro.d.ts +9 -1
- package/dist/compiler/astro.js +182 -3
- package/dist/compiler/cli.d.ts +13 -0
- package/dist/compiler/cli.js +28 -4
- package/dist/compiler/collections.d.ts +3 -2
- package/dist/compiler/collections.js +28 -1
- package/dist/compiler/course-config.d.ts +88 -1
- package/dist/compiler/course-config.js +35 -1
- package/dist/compiler/exports/standalone-html.d.ts +34 -0
- package/dist/compiler/exports/standalone-html.js +803 -0
- package/dist/compiler/manifest.d.ts +98 -6
- package/dist/compiler/manifest.js +154 -17
- 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.d.mts +10 -15
- package/dist/reference/equations.mjs +67 -14
- package/dist/reference/gate-abbreviations.d.ts +17 -0
- package/dist/reference/gate-abbreviations.js +64 -0
- package/dist/reference/gate-heading-length.d.ts +12 -0
- package/dist/reference/gate-heading-length.js +79 -0
- package/dist/reference/gate-math.d.ts +32 -5
- package/dist/reference/gate-math.js +6 -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 +5 -5
- package/dist/reference/keyed-jsx-floats.d.mts +7 -12
- package/dist/reference/keyed-jsx-floats.mjs +50 -25
- 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 +42 -0
- package/dist/reference/math-glyphs.mjs +548 -40
- 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-citation.d.mts +5 -1
- package/dist/reference/remark-citation.mjs +0 -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 +57 -9
- package/dist/reference/remark-notation.mjs +354 -42
- 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 +53 -4
- package/docs/authoring/README.md +10 -0
- package/docs/authoring/agents.md +17 -2
- package/docs/authoring/components.md +138 -0
- package/docs/authoring/migrating.md +275 -7
- package/docs/authoring/notation.md +615 -1
- package/docs/authoring/structure-rules.md +69 -0
- package/docs/authoring/structure.md +120 -8
- package/docs/authoring/visualizations.md +36 -3
- package/package.json +5 -2
- package/src/compiler/astro.ts +208 -3
- package/src/compiler/cli.ts +29 -2
- package/src/compiler/collections.ts +45 -4
- package/src/compiler/course-config.ts +132 -0
- package/src/compiler/exports/standalone-html.ts +963 -0
- package/src/compiler/manifest.ts +281 -22
- 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 +409 -103
- 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 +260 -83
- 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/LayoutSocialIcons.astro +41 -2
- package/src/components/starlight/LessonExportActions.astro +436 -0
- package/src/components/starlight/LessonFooter.astro +30 -3
- package/src/components/starlight/section-parts.ts +31 -0
- package/src/components/table/Table.astro +293 -7
- package/src/components/table/TableOverflowExpander.astro +489 -0
- package/src/content-config.ts +36 -1
- 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.mjs +67 -14
- package/src/reference/gate-abbreviations.ts +128 -0
- package/src/reference/gate-heading-length.ts +131 -0
- package/src/reference/gate-math.ts +32 -7
- package/src/reference/gate-prose-notation.ts +224 -0
- package/src/reference/keyed-float-validate.ts +5 -5
- package/src/reference/keyed-jsx-floats.mjs +50 -25
- 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 +548 -40
- 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-citation.mjs +0 -0
- package/src/reference/remark-memo.mjs +339 -0
- package/src/reference/remark-notation.mjs +354 -42
- 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 +996 -50
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Remark adapter for reveal-in-place short notes.
|
|
3
|
+
*
|
|
4
|
+
* A `<ShortNote>` may sit inside a sentence while its body contains block
|
|
5
|
+
* Markdown. Before mdast parsing, the adapter extracts that body and leaves an
|
|
6
|
+
* ordinary inline link placeholder behind. After parsing, it inserts the real
|
|
7
|
+
* flow component immediately after the intact paragraph. The browser enhancer
|
|
8
|
+
* can then split/rejoin that paragraph only while the note is open, preserving
|
|
9
|
+
* valid and genuinely inline collapsed markup.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { stripFencedCode } from './equations.mjs';
|
|
13
|
+
import { KEYED_FLOAT_KEY_PATTERN } from './keyed-jsx-floats.mjs';
|
|
14
|
+
import {
|
|
15
|
+
injectMdxStringAttributes,
|
|
16
|
+
mdxAttributesNamed,
|
|
17
|
+
removeMdxAttribute,
|
|
18
|
+
} from './mdx-attributes.mjs';
|
|
19
|
+
|
|
20
|
+
const BLOCK_ATTRIBUTE = 'data-explico-short-note-block';
|
|
21
|
+
const BLOCKS_DATA = 'explicoShortNoteBlocks';
|
|
22
|
+
const MARKER_URL_PREFIX = '#explico-internal-short-note-marker-';
|
|
23
|
+
const ATTRIBUTE_SOURCE = String.raw`(?:"[^"]*"|'[^']*'|\{(?:[^{}]|\{[^{}]*\})*\}|[^>"'])*`;
|
|
24
|
+
const SHORT_NOTE_PAIR = new RegExp(
|
|
25
|
+
String.raw`<ShortNote\b(${ATTRIBUTE_SOURCE})>([\s\S]*?)<\/ShortNote\s*>`,
|
|
26
|
+
'g',
|
|
27
|
+
);
|
|
28
|
+
|
|
29
|
+
function maskBlock(block) {
|
|
30
|
+
return block.replace(/[^\n]/g, ' ');
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function searchableSource(source) {
|
|
34
|
+
return stripFencedCode(source)
|
|
35
|
+
.replace(/<!--[\s\S]*?-->/g, maskBlock)
|
|
36
|
+
.replace(/\{\/\*[\s\S]*?\*\/\}/g, maskBlock);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function extractShortNotes(source) {
|
|
40
|
+
const input = String(source);
|
|
41
|
+
const searchable = searchableSource(input);
|
|
42
|
+
const matches = [...searchable.matchAll(SHORT_NOTE_PAIR)];
|
|
43
|
+
if (matches.length === 0) return { source: input, notes: [] };
|
|
44
|
+
|
|
45
|
+
const notes = [];
|
|
46
|
+
let cursor = 0;
|
|
47
|
+
let output = '';
|
|
48
|
+
for (let index = 0; index < matches.length; index++) {
|
|
49
|
+
const match = matches[index];
|
|
50
|
+
const start = match.index ?? 0;
|
|
51
|
+
if (start < cursor) continue;
|
|
52
|
+
|
|
53
|
+
const whole = match[0];
|
|
54
|
+
const maskedAttributes = match[1] ?? '';
|
|
55
|
+
const openLength = '<ShortNote'.length + maskedAttributes.length + 1;
|
|
56
|
+
const bodyStart = start + openLength;
|
|
57
|
+
const closeStart = start + whole.lastIndexOf('</ShortNote');
|
|
58
|
+
const searchableBody = searchable.slice(bodyStart, closeStart);
|
|
59
|
+
if (/<ShortNote\b/.test(searchableBody)) {
|
|
60
|
+
throw new Error('A <ShortNote> cannot be nested inside another <ShortNote>.');
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
notes.push({
|
|
64
|
+
token: String(index),
|
|
65
|
+
attributes: input.slice(start + '<ShortNote'.length, bodyStart - 1),
|
|
66
|
+
body: input.slice(bodyStart, closeStart),
|
|
67
|
+
});
|
|
68
|
+
output += input.slice(cursor, start);
|
|
69
|
+
output += `[short note](${MARKER_URL_PREFIX}${index})`;
|
|
70
|
+
cursor = start + whole.length;
|
|
71
|
+
}
|
|
72
|
+
output += input.slice(cursor);
|
|
73
|
+
return { source: output, notes };
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/** Return the parseable source form, primarily for adapter tests. */
|
|
77
|
+
export function preprocessShortNotes(source) {
|
|
78
|
+
return extractShortNotes(source).source;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function fail(file, message, node) {
|
|
82
|
+
if (typeof file?.fail === 'function') file.fail(message, node);
|
|
83
|
+
const location = file?.path ? `${file.path}: ` : '';
|
|
84
|
+
throw new Error(`${location}${message}`);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function literalAttribute(node, name, file) {
|
|
88
|
+
const matches = mdxAttributesNamed(node, name);
|
|
89
|
+
if (matches.length > 1) {
|
|
90
|
+
fail(file, `<ShortNote> has more than one \`${name}\` prop.`, node);
|
|
91
|
+
}
|
|
92
|
+
if (matches.length === 0) {
|
|
93
|
+
fail(file, `<ShortNote> requires a non-empty literal \`${name}\` prop.`, node);
|
|
94
|
+
}
|
|
95
|
+
const value = matches[0]?.value;
|
|
96
|
+
if (typeof value !== 'string' || !value.trim()) {
|
|
97
|
+
fail(
|
|
98
|
+
file,
|
|
99
|
+
`<ShortNote> \`${name}\` must be a non-empty literal string; expressions are not supported for inline note triggers.`,
|
|
100
|
+
matches[0] ?? node,
|
|
101
|
+
);
|
|
102
|
+
}
|
|
103
|
+
return value;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function markerNode(note) {
|
|
107
|
+
return {
|
|
108
|
+
type: 'link',
|
|
109
|
+
url: `#${note.anchorId}`,
|
|
110
|
+
children: [{ type: 'text', value: note.title }],
|
|
111
|
+
data: {
|
|
112
|
+
hProperties: {
|
|
113
|
+
className: ['short-note-marker'],
|
|
114
|
+
'data-short-note-key': note.id,
|
|
115
|
+
'aria-controls': note.anchorId,
|
|
116
|
+
'aria-expanded': 'false',
|
|
117
|
+
'aria-label': `${note.title}: ${note.description}`,
|
|
118
|
+
title: note.description,
|
|
119
|
+
},
|
|
120
|
+
},
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function visit(node, callback, parent) {
|
|
125
|
+
if (!node || typeof node !== 'object') return;
|
|
126
|
+
callback(node, parent);
|
|
127
|
+
if (!Array.isArray(node.children)) return;
|
|
128
|
+
for (const child of node.children) visit(child, callback, node);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/** @this {any} */
|
|
132
|
+
export default function remarkShortNote() {
|
|
133
|
+
const processor = this;
|
|
134
|
+
const parse = processor.parser;
|
|
135
|
+
if (typeof parse !== 'function') {
|
|
136
|
+
throw new Error(
|
|
137
|
+
'remarkShortNote must be registered after the Markdown parser (and after remark-mdx when configured explicitly).',
|
|
138
|
+
);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
Reflect.set(processor, 'parser', (document, file) => {
|
|
142
|
+
const extracted = extractShortNotes(document);
|
|
143
|
+
const tree = parse(extracted.source, file);
|
|
144
|
+
if (extracted.notes.length === 0) return tree;
|
|
145
|
+
|
|
146
|
+
const blocks = extracted.notes.map((note) => {
|
|
147
|
+
const source = `<ShortNote ${BLOCK_ATTRIBUTE}="${note.token}"${note.attributes}>\n\n${note.body}\n\n</ShortNote>`;
|
|
148
|
+
const parsed = parse(source, file);
|
|
149
|
+
const block = parsed.children?.find((node) => node?.name === 'ShortNote');
|
|
150
|
+
if (!block) {
|
|
151
|
+
fail(file, 'Unable to parse the extracted <ShortNote> body.', parsed);
|
|
152
|
+
}
|
|
153
|
+
return block;
|
|
154
|
+
});
|
|
155
|
+
tree.data ??= {};
|
|
156
|
+
tree.data[BLOCKS_DATA] = blocks;
|
|
157
|
+
return tree;
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
return function transform(tree, file) {
|
|
161
|
+
const blocks = tree.data?.[BLOCKS_DATA] ?? [];
|
|
162
|
+
if (blocks.length === 0) return;
|
|
163
|
+
|
|
164
|
+
const markers = new Map();
|
|
165
|
+
const parents = new Map();
|
|
166
|
+
visit(tree, (node, parent) => {
|
|
167
|
+
if (parent) parents.set(node, parent);
|
|
168
|
+
if (
|
|
169
|
+
node.type === 'link' &&
|
|
170
|
+
typeof node.url === 'string' &&
|
|
171
|
+
node.url.startsWith(MARKER_URL_PREFIX)
|
|
172
|
+
) {
|
|
173
|
+
const token = node.url.slice(MARKER_URL_PREFIX.length);
|
|
174
|
+
if (markers.has(token)) fail(file, 'Internal short-note marker collision.', node);
|
|
175
|
+
markers.set(token, { node, parent });
|
|
176
|
+
}
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
if (blocks.length !== markers.size) {
|
|
180
|
+
fail(file, 'Internal short-note marker/block pairing failed.', tree);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
const seenIds = new Set();
|
|
184
|
+
const insertions = new Map();
|
|
185
|
+
for (const block of blocks) {
|
|
186
|
+
const token = literalAttribute(block, BLOCK_ATTRIBUTE, file);
|
|
187
|
+
const marker = markers.get(token);
|
|
188
|
+
if (!marker?.parent || marker.parent.type !== 'paragraph') {
|
|
189
|
+
fail(file, '<ShortNote> must be placed in ordinary prose.', marker?.node ?? block);
|
|
190
|
+
}
|
|
191
|
+
const container = parents.get(marker.parent);
|
|
192
|
+
if (!container || !Array.isArray(container.children)) {
|
|
193
|
+
fail(file, 'A <ShortNote> paragraph has no flow parent.', marker.parent);
|
|
194
|
+
}
|
|
195
|
+
if (block.type !== 'mdxJsxFlowElement') {
|
|
196
|
+
fail(file, '<ShortNote> did not become a block-level MDX element.', block);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
const id = literalAttribute(block, 'id', file);
|
|
200
|
+
const title = literalAttribute(block, 'title', file);
|
|
201
|
+
const description = literalAttribute(block, 'description', file);
|
|
202
|
+
if (!KEYED_FLOAT_KEY_PATTERN.test(id)) {
|
|
203
|
+
fail(
|
|
204
|
+
file,
|
|
205
|
+
`Invalid <ShortNote id="${id}">; use lowercase letters/numbers separated by single hyphens.`,
|
|
206
|
+
block,
|
|
207
|
+
);
|
|
208
|
+
}
|
|
209
|
+
if (seenIds.has(id)) {
|
|
210
|
+
fail(file, `Duplicate <ShortNote id="${id}"> on this page.`, block);
|
|
211
|
+
}
|
|
212
|
+
seenIds.add(id);
|
|
213
|
+
|
|
214
|
+
const anchorId = `short-note-${id}`;
|
|
215
|
+
removeMdxAttribute(block, BLOCK_ATTRIBUTE);
|
|
216
|
+
injectMdxStringAttributes(block, { anchorId });
|
|
217
|
+
|
|
218
|
+
const markerIndex = marker.parent.children.indexOf(marker.node);
|
|
219
|
+
marker.parent.children[markerIndex] = markerNode({
|
|
220
|
+
id,
|
|
221
|
+
title,
|
|
222
|
+
description,
|
|
223
|
+
anchorId,
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
const existing = insertions.get(marker.parent) ?? { container, blocks: [] };
|
|
227
|
+
existing.blocks.push(block);
|
|
228
|
+
insertions.set(marker.parent, existing);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
for (const [paragraph, insertion] of insertions) {
|
|
232
|
+
const paragraphIndex = insertion.container.children.indexOf(paragraph);
|
|
233
|
+
insertion.container.children.splice(paragraphIndex + 1, 0, ...insertion.blocks);
|
|
234
|
+
}
|
|
235
|
+
delete tree.data[BLOCKS_DATA];
|
|
236
|
+
};
|
|
237
|
+
}
|
|
@@ -84,6 +84,21 @@ interface NotationRegistryInput {
|
|
|
84
84
|
readonly sharedDefinitions: readonly SharedNotationDefinitionInput[];
|
|
85
85
|
readonly lessons: readonly NotationLessonInput[];
|
|
86
86
|
}
|
|
87
|
+
/**
|
|
88
|
+
* One entry in the abbreviations table (N14) — deliberately a lighter tier
|
|
89
|
+
* than {@link SharedNotationDefinitionInput}: a name resolution only, no
|
|
90
|
+
* `formula`, `units`, `alignment`, or `sources`. `abbr` is the canonical
|
|
91
|
+
* written form (`OLS`); `aliases` are other written forms of the same
|
|
92
|
+
* abbreviation (`w.r.t.` might alias `wrt`) that should resolve the same way.
|
|
93
|
+
*/
|
|
94
|
+
interface AbbreviationDefinitionInput {
|
|
95
|
+
readonly key: string;
|
|
96
|
+
readonly abbr: string;
|
|
97
|
+
readonly expansion: string;
|
|
98
|
+
readonly aliases: readonly string[];
|
|
99
|
+
readonly domain?: string;
|
|
100
|
+
readonly source: SourceSpan;
|
|
101
|
+
}
|
|
87
102
|
type NotationDiagnosticCode = 'invalid-key' | 'duplicate-page' | 'duplicate-definition' | 'conflicting-definition' | 'undefined-reference' | 'reference-cycle' | 'unused-definition' | 'alignment-unknown-competency' | 'alignment-unknown-lesson' | 'alignment-introduction' | 'alignment-unavailable' | 'alignment-review-state' | 'gloss-collides-with-card';
|
|
88
103
|
type DiagnosticSeverity = 'error' | 'warning';
|
|
89
104
|
interface NotationDiagnostic {
|
|
@@ -171,4 +186,4 @@ interface NotationRegistry {
|
|
|
171
186
|
readonly diagnostics: readonly NotationDiagnostic[];
|
|
172
187
|
}
|
|
173
188
|
|
|
174
|
-
export type { DiagnosticSeverity, EditorialStatus, LocalNotationDefinitionInput, LocalNotationDefinitionRecord, NotationAlignment, NotationBacklink, NotationBinding, NotationDefinitionRecord, NotationDefinitionScope, NotationDiagnostic, NotationDiagnosticCode, NotationGlossEntry, NotationLessonInput, NotationPageBundle, NotationReferenceInput, NotationReferenceKind, NotationRegistry, NotationRegistryInput, ResolvedNotationReference, SharedNotationDefinitionInput, SharedNotationDefinitionRecord, SourceSpan };
|
|
189
|
+
export type { AbbreviationDefinitionInput, DiagnosticSeverity, EditorialStatus, LocalNotationDefinitionInput, LocalNotationDefinitionRecord, NotationAlignment, NotationBacklink, NotationBinding, NotationDefinitionRecord, NotationDefinitionScope, NotationDiagnostic, NotationDiagnosticCode, NotationGlossEntry, NotationLessonInput, NotationPageBundle, NotationReferenceInput, NotationReferenceKind, NotationRegistry, NotationRegistryInput, ResolvedNotationReference, SharedNotationDefinitionInput, SharedNotationDefinitionRecord, SourceSpan };
|
package/docs/VERSIONING.md
CHANGED
|
@@ -48,15 +48,64 @@ every one is listed under a `### Breaking` heading in `CHANGELOG.md` for that
|
|
|
48
48
|
version. A **PATCH** never breaks. Once the API settles this clause is dropped
|
|
49
49
|
and normal semver applies.
|
|
50
50
|
|
|
51
|
+
## The migration artifact
|
|
52
|
+
|
|
53
|
+
Every published engine version carries agent-readable upgrade instructions in
|
|
54
|
+
[`authoring/migrating.md`](authoring/migrating.md). That file is part of the npm
|
|
55
|
+
package, so the instructions travel with the exact implementation they describe
|
|
56
|
+
and remain available at
|
|
57
|
+
`node_modules/explico/docs/authoring/migrating.md` after installation.
|
|
58
|
+
|
|
59
|
+
A release must add a newest-first `## <previous range> → <new version>` section,
|
|
60
|
+
whether the bump is MAJOR, MINOR, or PATCH. The section must do one of two things:
|
|
61
|
+
|
|
62
|
+
- give concrete, ordered changes for every affected consumer-owned surface —
|
|
63
|
+
dependency declarations, `content/`, `content/course.config.ts`, Astro/source
|
|
64
|
+
wiring, course scripts, and generated artifacts — including conditional cases
|
|
65
|
+
and verification commands; or
|
|
66
|
+
- state explicitly that no course changes are required, followed by the normal
|
|
67
|
+
verification commands.
|
|
68
|
+
|
|
69
|
+
**Every build-enforced/authoring rule and every manifest-schema bump the
|
|
70
|
+
release's `CHANGELOG.md` entry names gets its own migration coverage.** 0.5.0
|
|
71
|
+
shipped with only rule N14 and the schema-version bump covered — N9, N10, N13,
|
|
72
|
+
and N15 were all in the changelog and all missing from `migrating.md`, and the
|
|
73
|
+
schema-chain statement itself skipped the v8 and v9 bumps. A migration section
|
|
74
|
+
that has *some* ordered steps is not the same as a *complete* one: derive it by
|
|
75
|
+
walking the release's complete changelog entry line by line, not just its
|
|
76
|
+
headline items. `pnpm check:release-migration` now cross-checks this
|
|
77
|
+
mechanically (see below) — it greps the changelog entry for `rule N9`-style
|
|
78
|
+
mentions and `is now v9`-style schema bumps and fails if the matching version's
|
|
79
|
+
`migrating.md` section doesn't name each one the same way (`rule N9`, `v9`).
|
|
80
|
+
Keep both files' phrasing in that shape; a rule discussed without the word
|
|
81
|
+
"rule" immediately before its ID, or a schema bump not written `is now vN`,
|
|
82
|
+
won't be picked up by either side of the check.
|
|
83
|
+
|
|
84
|
+
The changelog explains what changed and why; it is not a substitute for the
|
|
85
|
+
migration section. The migration section, changelog entry, version bump, and
|
|
86
|
+
package are one atomic release. An agent upgrading across several versions
|
|
87
|
+
applies every intervening section in order.
|
|
88
|
+
|
|
51
89
|
## Cutting a release
|
|
52
90
|
|
|
53
91
|
1. Decide the next version from the rules above and the `## [Unreleased]`
|
|
54
92
|
section of `CHANGELOG.md`.
|
|
55
|
-
2.
|
|
93
|
+
2. Add the section for `x.y.z` to `docs/authoring/migrating.md`, derived from the
|
|
94
|
+
complete release diff. Write explicit no-action instructions when nothing in
|
|
95
|
+
a downstream course needs to change. Walk the `CHANGELOG.md` entry you're
|
|
96
|
+
about to finalize bullet by bullet and give every `rule N#` it names, and
|
|
97
|
+
every `is now vN` schema bump, its own coverage — not only the entry's
|
|
98
|
+
headline item.
|
|
99
|
+
3. `CHANGELOG.md`: rename `## [Unreleased]` to `## [x.y.z] - YYYY-MM-DD`, add a
|
|
56
100
|
fresh empty `## [Unreleased]`, and fix up the compare links at the bottom.
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
101
|
+
4. Bump `package.json` `version` (`npm version x.y.z --no-git-tag-version`).
|
|
102
|
+
5. Run `pnpm check:release-migration`. It fails when the new version has no
|
|
103
|
+
migration section, the shipped-file configuration excludes the guide, or a
|
|
104
|
+
`rule N#` / `is now vN` mention in the `CHANGELOG.md` entry has no match in
|
|
105
|
+
the `migrating.md` section.
|
|
106
|
+
6. Run `pnpm build && pnpm test`, then inspect `pnpm pack --dry-run` and confirm
|
|
107
|
+
that `docs/authoring/migrating.md` and `docs/authoring/agents.md` are listed.
|
|
108
|
+
7. Commit, then `git tag vx.y.z`.
|
|
60
109
|
|
|
61
110
|
## When you change the engine
|
|
62
111
|
|
package/docs/authoring/README.md
CHANGED
|
@@ -74,6 +74,8 @@ file table.
|
|
|
74
74
|
| [notation.md](notation.md) | `N` | how math symbols are written and bound |
|
|
75
75
|
| [visualizations.md](visualizations.md) | `V` | diagrams, charts, interactive figures, randomness |
|
|
76
76
|
| [versioning.md](versioning.md) | `C` | the knowledge version and `content/CHANGELOG.md` |
|
|
77
|
+
| [structure-rules.md](structure-rules.md) | `S` | heading and figure/diagram/memo/short-note title length |
|
|
78
|
+
| [components.md](components.md) | — | _catalog_: inline/block features available in a lesson body — tooltips, tables, figures, diagrams, memos, abbreviations, citations |
|
|
77
79
|
| [agents.md](agents.md) | — | _guide_: working on a course repo (with an AI agent) |
|
|
78
80
|
| [migrating.md](migrating.md) | — | _guide_: per-version steps when bumping the `explico` dep |
|
|
79
81
|
|
|
@@ -89,6 +91,13 @@ file table.
|
|
|
89
91
|
| [N6](notation.md#n6) | Probability is `\mathbb{P}` (double-struck `P`) | Build-enforced (completeness gate) + review |
|
|
90
92
|
| [N7](notation.md#n7) | The differential `d` is upright `\mathrm{d}` | Build-enforced (completeness gate) |
|
|
91
93
|
| [N8](notation.md#n8) | A named operator is universal (shipped/course list) or explained | Tier-3 warning (`operator-explained`) |
|
|
94
|
+
| [N9](notation.md#n9) | A quantity/unit/operator is typeset in `$…$`, never as plain text | Build-enforced (`gate-prose-notation.ts`) |
|
|
95
|
+
| [N10](notation.md#n10) | A `\mathrm{…}` multi-letter label is universal, or it is explained | Tier-3 warning (`roman-label-explained`) |
|
|
96
|
+
| [N11](notation.md#n11) | A worked example never drops a unit from a substituted value | Review-only |
|
|
97
|
+
| [N12](notation.md#n12) | Math syntax never carries the sentence's own trailing punctuation | Review-only |
|
|
98
|
+
| [N13](notation.md#n13) | A nonlinear function always takes a parenthesized argument | Build-enforced (`resolveMathGlyphs`) |
|
|
99
|
+
| [N14](notation.md#n14) | An abbreviation-shaped word in prose is universal, or explained | Build-enforced (`gate-abbreviations.ts`) |
|
|
100
|
+
| [N15](notation.md#n15) | A nested `(`, `[`, `{`, or `\|` must use `\left`/`\right` sizing | Build-enforced (`resolveMathGlyphs`) |
|
|
92
101
|
| [V1](visualizations.md#v1) | Graphs and flowcharts use the `Diagram` component | Review-only |
|
|
93
102
|
| [V2](visualizations.md#v2) | Static charts: SVG → D3 → Observable Plot, chosen per figure | Review-only |
|
|
94
103
|
| [V3](visualizations.md#v3) | Interactive figures: island + `renderLabMath` seam + text alt | Review-only |
|
|
@@ -98,3 +107,4 @@ file table.
|
|
|
98
107
|
| [C2](versioning.md#c2) | The knowledge version is semver over learning-facing content | Review-only |
|
|
99
108
|
| [C3](versioning.md#c3) | `about.version` equals the newest `content/CHANGELOG.md` heading | Build-enforced (Tier-3 `knowledge-version-mismatch`) |
|
|
100
109
|
| [C4](versioning.md#c4) | On release, move `[Unreleased]` and bump `about.version` together | Review-only |
|
|
110
|
+
| [S1](structure-rules.md#s1) | No heading, or figure/diagram/memo/short-note title, over 65 characters | Build-enforced (`gate-heading-length.ts`) for body/floats + review for `frontmatter.title` |
|
package/docs/authoring/agents.md
CHANGED
|
@@ -30,6 +30,9 @@ and a rule disagree, the engine wins — see [`README.md`](README.md).
|
|
|
30
30
|
- Grep for a symbol and open a line range; don't read a whole engine module out
|
|
31
31
|
of `node_modules/explico/`. The parts you actually need are
|
|
32
32
|
[`notation.md`](notation.md), this file, and the `content` CLI output.
|
|
33
|
+
- Not sure what's available to put in a lesson body — a tooltip, a table, a
|
|
34
|
+
diagram, an aside? [`components.md`](components.md) is a syntax-first
|
|
35
|
+
catalog, not another rule file to read cover to cover.
|
|
33
36
|
- If a page 500s or a change doesn't show, the dev server's content store is
|
|
34
37
|
stale — a **new file in a `content/` collection needs a dev-server restart**,
|
|
35
38
|
not just a save. Restart it; don't diagnose it with `ps` / `curl` scrapes.
|
|
@@ -59,5 +62,17 @@ sets `about.version` to match, in the same commit. Rules C1–C4,
|
|
|
59
62
|
|
|
60
63
|
## Upgrading the engine
|
|
61
64
|
|
|
62
|
-
|
|
63
|
-
|
|
65
|
+
Treat every `explico` dependency bump as a migration task, including PATCH
|
|
66
|
+
updates. Before changing the dependency, record the installed version with
|
|
67
|
+
`node -p "require('explico/package.json').version"`. Install the target version,
|
|
68
|
+
then read the target package's
|
|
69
|
+
`node_modules/explico/docs/authoring/migrating.md`; the migration instructions
|
|
70
|
+
ship with the code they describe.
|
|
71
|
+
|
|
72
|
+
Apply every version section after the old version through the installed version,
|
|
73
|
+
oldest first. Follow required and conditional changes across `content/`,
|
|
74
|
+
`content/course.config.ts`, Astro/source wiring, course scripts, dependencies,
|
|
75
|
+
and generated files. Do not use `CHANGELOG.md` alone as the procedure and do not
|
|
76
|
+
assume that a release without a **Breaking** heading needs no work; each migration
|
|
77
|
+
section either gives exact work or explicitly says no course changes are
|
|
78
|
+
required. Finish with the validation sequence in [`migrating.md`](migrating.md).
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
# Inline & block features — a catalog
|
|
2
|
+
|
|
3
|
+
A fast index of what can go in a lesson body, for an AI (or a person) drafting
|
|
4
|
+
content without reading every rule file first. This file is not a rule area —
|
|
5
|
+
nothing here is build-enforced by *this* file. Where a feature has a rule
|
|
6
|
+
area (`N`, `V`, `C`), the enforcement and the rationale live there; this file
|
|
7
|
+
only says what exists, its minimal syntax, and which of several
|
|
8
|
+
similar-looking options to reach for.
|
|
9
|
+
|
|
10
|
+
See [README.md](README.md) for what "Build-enforced" / "Review-only" means,
|
|
11
|
+
[structure.md](structure.md) for the five structural levels and how floats
|
|
12
|
+
are numbered, and [agents.md](agents.md) for the day-to-day workflow (CLI,
|
|
13
|
+
validation loop).
|
|
14
|
+
|
|
15
|
+
## Inline, zero-registration
|
|
16
|
+
|
|
17
|
+
**A native browser tooltip: `title="…"` on any element.**
|
|
18
|
+
|
|
19
|
+
```mdx
|
|
20
|
+
The fit uses <span title="Ordinary least squares run on the demeaned series">OLS</span> here.
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
This is plain HTML passed straight through MDX/JSX to the DOM — no `id`, no
|
|
24
|
+
content-collection entry, no build-time registration, no hover-card styling,
|
|
25
|
+
no click-to-pin behaviour. The browser renders its own default tooltip on
|
|
26
|
+
hover/focus, exactly like a native `<abbr title="…">` or an image `alt`
|
|
27
|
+
tooltip would. Use it for a throwaway aside that doesn't deserve a citable,
|
|
28
|
+
numbered, or glossary-linked definition — a wording nudge, a "why this value"
|
|
29
|
+
note, a translation of a variable name.
|
|
30
|
+
|
|
31
|
+
_How to apply._ Any element takes `title`; `<span>` is the usual wrapper for
|
|
32
|
+
bare prose text that has no other markup reason to exist. `title` is a plain
|
|
33
|
+
DOM string: no `$…$` math rendering happens inside it (same carve-out
|
|
34
|
+
[N9](notation.md#n9) documents for `<Figure caption="…">` / `<Table
|
|
35
|
+
caption="…">`) — write a unit as a word or plain Unicode, not LaTeX.
|
|
36
|
+
|
|
37
|
+
_What it does **not** replace:_
|
|
38
|
+
|
|
39
|
+
- **An abbreviation-shaped word in visible prose still has to satisfy
|
|
40
|
+
[N14](notation.md#n14).** The completeness gates blank out JSX *tags*
|
|
41
|
+
(`<span title="…">…</span>` → the `<span …>` and `</span>` markup) but not
|
|
42
|
+
the *text a reader sees between them* — so wrapping `OLS` in a tooltip span
|
|
43
|
+
does not exempt that visible `OLS` from needing a real
|
|
44
|
+
`content/abbreviations/*.md` entry, a notation binding, or a
|
|
45
|
+
`course.config.ts` allowlist entry. `title="…"` adds a hover hint on top of
|
|
46
|
+
an otherwise-explained term; it is not itself an explanation the gate
|
|
47
|
+
recognizes. If the word only ever needs to exist inside the tooltip string
|
|
48
|
+
(never as the visible text), the gate never sees it and there is nothing to
|
|
49
|
+
satisfy.
|
|
50
|
+
- **A math quantity still goes in `$…$`, not a `title`.** A hover *card* with
|
|
51
|
+
the definition, one per glyph, page-wide — that's `[[key]]` / notation
|
|
52
|
+
entries ([N4](notation.md#n4)), unrelated machinery.
|
|
53
|
+
- **A citable, reusable, or numbered explanation** — something another page
|
|
54
|
+
should be able to link to, or that belongs in the glossary — is a
|
|
55
|
+
[`<ShortNote>`/`<Memo>`](#short-asides-and-detours) or an
|
|
56
|
+
[abbreviation entry](#reference-systems), not a tooltip. A `title` string
|
|
57
|
+
is invisible to search, screen-reader users relying on non-hover
|
|
58
|
+
interaction, and print/PDF export.
|
|
59
|
+
|
|
60
|
+
_Enforcement._ None — this is unregistered, ungated HTML. The only thing that
|
|
61
|
+
still applies is N9/N14 against the *visible* text, as above.
|
|
62
|
+
|
|
63
|
+
## Short asides and detours
|
|
64
|
+
|
|
65
|
+
| Component | Import | What it's for | Doc |
|
|
66
|
+
| --- | --- | --- | --- |
|
|
67
|
+
| `<ShortNote id="…" title="…" description="…">` | `explico/components/memo/ShortNote.astro` | An inline, click-to-expand aside that replaces its own trigger text in place — "the smaller, genuinely inline interaction." No number, no cross-page reference. | [structure.md](structure.md#detailed-memos-and-inline-short-notes) |
|
|
68
|
+
| `<Memo id="…" title="…" description="…">` | `explico/components/memo/Memo.astro` | A collapsible, independently-numbered card for optional depth (derivation, proof, edge case) that would otherwise bloat the main argument. Cited with `[[memo-id]]`. | [structure.md](structure.md#detailed-memos-and-inline-short-notes) |
|
|
69
|
+
|
|
70
|
+
Both differ from a bare `title` tooltip in the same way: they hold real,
|
|
71
|
+
possibly multi-paragraph Markdown content (including equations, tables,
|
|
72
|
+
figures), are addressable (`id`, a permalink), and are meant to be found
|
|
73
|
+
again — a `title` tooltip is disposable and invisible outside a hover/focus
|
|
74
|
+
event.
|
|
75
|
+
|
|
76
|
+
## Numbered floats
|
|
77
|
+
|
|
78
|
+
Each of these gets a `‹part›.‹n›` number (its own counter, independent of the
|
|
79
|
+
others — see [structure.md § Numbering](structure.md#numbering)), a stable
|
|
80
|
+
`#anchor`, and same-page/cross-page reference syntax `[[prefix-id]]` /
|
|
81
|
+
`[[lesson/slug#prefix-id]]`.
|
|
82
|
+
|
|
83
|
+
| Component | Import | Prefix | Doc |
|
|
84
|
+
| --- | --- | --- | --- |
|
|
85
|
+
| `<Table id="…" caption="…">` | `explico/components/table/Table.astro` | `tab-` | [structure.md](structure.md#numbering), [V rules](visualizations.md) |
|
|
86
|
+
| `<Figure id="…" title="…" caption="…" previewable>` | `explico/components/figure/Figure.astro` | `fig-` | [structure.md](structure.md#numbering), [V2](visualizations.md#v2)/[V3](visualizations.md#v3) |
|
|
87
|
+
| `<Diagram id="…" label="…" nodes={…} edges={…}>` | `explico/components/diagram/Diagram.astro` | `dia-` | [V1](visualizations.md#v1) |
|
|
88
|
+
| A display equation with `\label{eq:key}` | `remark-math` (built in) | `eq-` | [N5](notation.md#n5), [structure.md](structure.md#numbering) |
|
|
89
|
+
|
|
90
|
+
Two naming traps worth flagging for an AI author:
|
|
91
|
+
|
|
92
|
+
- **`<Figure title="…">` is a visible short label in the collapsible title
|
|
93
|
+
bar, not the HTML `title` attribute's hover tooltip.** It always renders on
|
|
94
|
+
the page; it is a different feature from the [native tooltip](#inline-zero-registration)
|
|
95
|
+
above even though it reuses the same prop name. Keep it short — the whole
|
|
96
|
+
caption sentence does not belong here; see [S1](structure-rules.md#s1). An
|
|
97
|
+
omitted `title` silently falls back to `caption` in full, which is exactly
|
|
98
|
+
the failure S1 exists to catch.
|
|
99
|
+
- **A raw Markdown pipe table or a raw `<table>`, or a `<Diagram>` with no
|
|
100
|
+
`id`,** renders fine but never enters keyed-float validation — no number,
|
|
101
|
+
no `[[…]]` target. Wrap it (`<Table>`/`<Figure>`) or give the diagram an
|
|
102
|
+
`id` if it needs to be referenced. See [structure.md § Raw and unwrapped
|
|
103
|
+
floats](structure.md#raw-and-unwrapped-floats).
|
|
104
|
+
|
|
105
|
+
## Reference systems
|
|
106
|
+
|
|
107
|
+
These bind a term to a definition that a reader can hover *and* look up
|
|
108
|
+
elsewhere — the machinery a bare `title` tooltip deliberately skips.
|
|
109
|
+
|
|
110
|
+
| Syntax | Registers where | Gives | Doc |
|
|
111
|
+
| --- | --- | --- | --- |
|
|
112
|
+
| `[[key]]` in prose, before first math use | `content/notation/*.md` entry | One-meaning-per-glyph hover card everywhere `$key$`-bound math appears on the page | [N4](notation.md#n4) |
|
|
113
|
+
| `\explain{key}{latex}` inside `$…$` | Same notation entry | Escape hatch for a compound/colliding symbol only, not the default bind | [N4](notation.md#n4) |
|
|
114
|
+
| A bare all-caps/dotted term (`OLS`, `w.r.t.`) in prose | `content/abbreviations/*.md`, or `course.config.ts` `abbreviations.allow` | Hover/click card on first occurrence, linked to the glossary page | [N14](notation.md#n14) |
|
|
115
|
+
| `[@source-id]` or `[@source-id; §1.2]` | `content/sources/*.json` | Numbered `[n]` superscript linking to a References section, with a hover/pin panel | `src/reference/remark-citation.mjs` |
|
|
116
|
+
|
|
117
|
+
## Knowledge checks and worked examples
|
|
118
|
+
|
|
119
|
+
| Component | Import | Notes | Doc |
|
|
120
|
+
| --- | --- | --- | --- |
|
|
121
|
+
| `<AssessmentSet />` | `explico/components/assessments/AssessmentSet.astro` | Renders every check listed in the lesson's colocated `.checks.yml`; do not add a separate heading for it | [structure.md § Knowledge checks](structure.md#knowledge-checks) |
|
|
122
|
+
| `<CompactExample>` / `<CompactExamples>` | `explico/components/examples/CompactExample.astro` | Worked-example cards; a `<Diagram>` nested inside one is invisible to diagram numbering (see the raw/unwrapped note above) | [structure.md](structure.md#raw-and-unwrapped-floats) |
|
|
123
|
+
|
|
124
|
+
## Choosing between the "explain a term" options
|
|
125
|
+
|
|
126
|
+
From lightest to heaviest — pick the first one that's actually enough:
|
|
127
|
+
|
|
128
|
+
1. **Native `title="…"` tooltip** — disposable, hover-only, nothing to
|
|
129
|
+
register. Use when the aside doesn't need to be found again.
|
|
130
|
+
2. **`<ShortNote>`** — inline, click-to-expand, can hold Markdown, no number.
|
|
131
|
+
Use when the aside is worth more than a one-line hover but still belongs
|
|
132
|
+
inline, not as its own citable unit.
|
|
133
|
+
3. **Abbreviation entry / notation `[[key]]`** — page-wide hover card, build
|
|
134
|
+
gated (N14/N4), listed in the glossary. Use when the term recurs, is
|
|
135
|
+
acronym-shaped, or is genuine math notation.
|
|
136
|
+
4. **`<Memo>`** — numbered, permalinked, holds real sub-content (proofs,
|
|
137
|
+
figures, tables). Use when the aside is long enough to be its own small
|
|
138
|
+
section.
|