explico 0.4.0 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (143) hide show
  1. package/CHANGELOG.md +273 -1
  2. package/README.md +103 -8
  3. package/dist/compiler/astro.d.ts +9 -1
  4. package/dist/compiler/astro.js +182 -3
  5. package/dist/compiler/cli.d.ts +13 -0
  6. package/dist/compiler/cli.js +28 -4
  7. package/dist/compiler/collections.d.ts +3 -2
  8. package/dist/compiler/collections.js +28 -1
  9. package/dist/compiler/course-config.d.ts +58 -1
  10. package/dist/compiler/course-config.js +28 -1
  11. package/dist/compiler/exports/standalone-html.d.ts +34 -0
  12. package/dist/compiler/exports/standalone-html.js +803 -0
  13. package/dist/compiler/manifest.d.ts +90 -6
  14. package/dist/compiler/manifest.js +150 -17
  15. package/dist/compiler/resolution-report.d.ts +4 -0
  16. package/dist/reference/base-abbreviations.d.mts +30 -0
  17. package/dist/reference/base-abbreviations.mjs +60 -0
  18. package/dist/reference/base-operators.d.mts +15 -0
  19. package/dist/reference/base-operators.mjs +40 -0
  20. package/dist/reference/base-roman-labels.d.mts +35 -0
  21. package/dist/reference/base-roman-labels.mjs +43 -0
  22. package/dist/reference/consistency.d.ts +8 -2
  23. package/dist/reference/consistency.js +50 -3
  24. package/dist/reference/diagrams-validate.d.ts +21 -0
  25. package/dist/reference/diagrams-validate.js +13 -0
  26. package/dist/reference/equations.d.mts +10 -15
  27. package/dist/reference/equations.mjs +67 -14
  28. package/dist/reference/gate-abbreviations.d.ts +17 -0
  29. package/dist/reference/gate-abbreviations.js +64 -0
  30. package/dist/reference/gate-math.d.ts +28 -5
  31. package/dist/reference/gate-math.js +5 -2
  32. package/dist/reference/gate-prose-notation.d.ts +60 -0
  33. package/dist/reference/gate-prose-notation.js +125 -0
  34. package/dist/reference/keyed-float-validate.d.ts +5 -5
  35. package/dist/reference/keyed-jsx-floats.d.mts +7 -12
  36. package/dist/reference/keyed-jsx-floats.mjs +50 -25
  37. package/dist/reference/knowledge-checks-validate.d.ts +35 -0
  38. package/dist/reference/knowledge-checks-validate.js +78 -0
  39. package/dist/reference/knowledge-checks.d.mts +25 -0
  40. package/dist/reference/knowledge-checks.mjs +45 -0
  41. package/dist/reference/lab-math-scope.d.ts +9 -1
  42. package/dist/reference/lab-math-scope.js +32 -4
  43. package/dist/reference/math-glyphs.d.mts +30 -0
  44. package/dist/reference/math-glyphs.mjs +367 -0
  45. package/dist/reference/mdx-attributes.d.mts +13 -0
  46. package/dist/reference/mdx-attributes.mjs +38 -0
  47. package/dist/reference/mixed-content.d.ts +17 -0
  48. package/dist/reference/mixed-content.js +29 -0
  49. package/dist/reference/page-notation.d.ts +52 -0
  50. package/dist/reference/page-notation.js +171 -0
  51. package/dist/reference/raw-floats-validate.d.ts +18 -0
  52. package/dist/reference/raw-floats-validate.js +53 -0
  53. package/dist/reference/raw-floats.d.mts +18 -0
  54. package/dist/reference/raw-floats.mjs +129 -0
  55. package/dist/reference/rehype-heading-anchors.d.mts +1 -1
  56. package/dist/reference/rehype-heading-anchors.mjs +107 -1
  57. package/dist/reference/rehype-table-header-labels.d.mts +26 -0
  58. package/dist/reference/rehype-table-header-labels.mjs +152 -0
  59. package/dist/reference/remark-abbreviations.d.mts +7 -0
  60. package/dist/reference/remark-abbreviations.mjs +174 -0
  61. package/dist/reference/remark-memo.d.mts +16 -0
  62. package/dist/reference/remark-memo.mjs +339 -0
  63. package/dist/reference/remark-notation.d.mts +57 -9
  64. package/dist/reference/remark-notation.mjs +354 -42
  65. package/dist/reference/remark-short-note.d.mts +4 -0
  66. package/dist/reference/remark-short-note.mjs +237 -0
  67. package/dist/reference/types.d.ts +16 -1
  68. package/dist/reference/units-vocab.js +4 -0
  69. package/docs/VERSIONING.md +33 -4
  70. package/docs/authoring/README.md +8 -0
  71. package/docs/authoring/agents.md +17 -2
  72. package/docs/authoring/components.md +135 -0
  73. package/docs/authoring/migrating.md +92 -7
  74. package/docs/authoring/notation.md +560 -1
  75. package/docs/authoring/structure.md +120 -8
  76. package/docs/authoring/visualizations.md +36 -3
  77. package/package.json +5 -2
  78. package/src/compiler/astro.ts +208 -3
  79. package/src/compiler/cli.ts +29 -2
  80. package/src/compiler/collections.ts +45 -4
  81. package/src/compiler/course-config.ts +92 -0
  82. package/src/compiler/exports/standalone-html.ts +963 -0
  83. package/src/compiler/manifest.ts +268 -22
  84. package/src/components/HoverPanel.astro +45 -4
  85. package/src/components/abbreviation/AbbreviationGlossary.astro +70 -0
  86. package/src/components/abbreviation/AbbreviationLayer.astro +127 -0
  87. package/src/components/assessments/AssessmentRunner.tsx +52 -17
  88. package/src/components/assessments/AssessmentSet.astro +98 -14
  89. package/src/components/assessments/RenderedContent.tsx +55 -0
  90. package/src/components/assessments/assessment-randomization.ts +16 -0
  91. package/src/components/diagram/Diagram.astro +444 -83
  92. package/src/components/disclosure/hover-preview.ts +202 -0
  93. package/src/components/disclosure/persisted-disclosure.ts +66 -0
  94. package/src/components/examples/CompactExamples.astro +2 -0
  95. package/src/components/figure/Figure.astro +279 -10
  96. package/src/components/memo/Memo.astro +453 -0
  97. package/src/components/memo/ShortNote.astro +223 -0
  98. package/src/components/notation/NotationFormula.astro +18 -1
  99. package/src/components/notation/NotationLayer.astro +105 -8
  100. package/src/components/notation/render-notation-math.ts +39 -2
  101. package/src/components/notation/safe-math-to-react.ts +50 -0
  102. package/src/components/reference/FloatRefLayer.astro +233 -15
  103. package/src/components/starlight/CollapsibleSections.astro +461 -0
  104. package/src/components/starlight/DiagramEnhancer.astro +385 -0
  105. package/src/components/starlight/HeadingAnchorEnhancer.astro +5 -17
  106. package/src/components/starlight/KeyedFloatEnhancer.astro +81 -3
  107. package/src/components/starlight/LayoutHeader.astro +50 -0
  108. package/src/components/starlight/LayoutSocialIcons.astro +41 -2
  109. package/src/components/starlight/LessonExportActions.astro +436 -0
  110. package/src/components/starlight/LessonFooter.astro +27 -2
  111. package/src/components/starlight/section-parts.ts +31 -0
  112. package/src/components/table/Table.astro +293 -7
  113. package/src/components/table/TableOverflowExpander.astro +489 -0
  114. package/src/content-config.ts +20 -0
  115. package/src/reference/base-abbreviations.mjs +60 -0
  116. package/src/reference/base-operators.mjs +40 -0
  117. package/src/reference/base-roman-labels.mjs +43 -0
  118. package/src/reference/consistency.ts +73 -2
  119. package/src/reference/diagrams-validate.ts +40 -0
  120. package/src/reference/equations.mjs +67 -14
  121. package/src/reference/gate-abbreviations.ts +128 -0
  122. package/src/reference/gate-math.ts +27 -7
  123. package/src/reference/gate-prose-notation.ts +224 -0
  124. package/src/reference/keyed-float-validate.ts +5 -5
  125. package/src/reference/keyed-jsx-floats.mjs +50 -25
  126. package/src/reference/knowledge-checks-validate.ts +130 -0
  127. package/src/reference/knowledge-checks.mjs +45 -0
  128. package/src/reference/lab-math-scope.ts +45 -3
  129. package/src/reference/math-glyphs.mjs +367 -0
  130. package/src/reference/mdx-attributes.mjs +38 -0
  131. package/src/reference/mixed-content.ts +69 -0
  132. package/src/reference/page-notation.ts +252 -0
  133. package/src/reference/raw-floats-validate.ts +103 -0
  134. package/src/reference/raw-floats.mjs +129 -0
  135. package/src/reference/rehype-heading-anchors.mjs +107 -1
  136. package/src/reference/rehype-table-header-labels.mjs +152 -0
  137. package/src/reference/remark-abbreviations.mjs +174 -0
  138. package/src/reference/remark-memo.mjs +339 -0
  139. package/src/reference/remark-notation.mjs +354 -42
  140. package/src/reference/remark-short-note.mjs +237 -0
  141. package/src/reference/types.ts +16 -0
  142. package/src/reference/units-vocab.ts +4 -0
  143. 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
+ }
@@ -96,6 +96,22 @@ export interface NotationRegistryInput {
96
96
  readonly lessons: readonly NotationLessonInput[];
97
97
  }
98
98
 
99
+ /**
100
+ * One entry in the abbreviations table (N14) — deliberately a lighter tier
101
+ * than {@link SharedNotationDefinitionInput}: a name resolution only, no
102
+ * `formula`, `units`, `alignment`, or `sources`. `abbr` is the canonical
103
+ * written form (`OLS`); `aliases` are other written forms of the same
104
+ * abbreviation (`w.r.t.` might alias `wrt`) that should resolve the same way.
105
+ */
106
+ export interface AbbreviationDefinitionInput {
107
+ readonly key: string;
108
+ readonly abbr: string;
109
+ readonly expansion: string;
110
+ readonly aliases: readonly string[];
111
+ readonly domain?: string;
112
+ readonly source: SourceSpan;
113
+ }
114
+
99
115
  export type NotationDiagnosticCode =
100
116
  | 'invalid-key'
101
117
  | 'duplicate-page'
@@ -123,6 +123,10 @@ export const UNITS_VOCAB: ReadonlySet<string> = new Set([
123
123
  'meters',
124
124
  'second',
125
125
  'seconds',
126
+ 'kilogram',
127
+ 'kilograms',
128
+ 'joule',
129
+ 'joules',
126
130
  'speed',
127
131
  'velocity',
128
132
  'acceleration',