explico 0.1.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 (113) hide show
  1. package/README.md +108 -0
  2. package/dist/analytics/AnalyticsEmitter.d.ts +22 -0
  3. package/dist/analytics/AnalyticsEmitter.js +5 -0
  4. package/dist/compiler/cli.d.ts +189 -0
  5. package/dist/compiler/cli.js +552 -0
  6. package/dist/compiler/collections.d.ts +70 -0
  7. package/dist/compiler/collections.js +383 -0
  8. package/dist/compiler/course-config.d.ts +31 -0
  9. package/dist/compiler/course-config.js +0 -0
  10. package/dist/compiler/lesson-derivation.d.ts +89 -0
  11. package/dist/compiler/lesson-derivation.js +91 -0
  12. package/dist/compiler/manifest.d.ts +178 -0
  13. package/dist/compiler/manifest.js +345 -0
  14. package/dist/compiler/resolution-report.d.ts +45 -0
  15. package/dist/compiler/resolution-report.js +97 -0
  16. package/dist/curriculum/validation.d.ts +76 -0
  17. package/dist/curriculum/validation.js +320 -0
  18. package/dist/progress/ProgressRepository.d.ts +53 -0
  19. package/dist/progress/ProgressRepository.js +12 -0
  20. package/dist/reference/base-library.mjs +27 -0
  21. package/dist/reference/citation-format.mjs +133 -0
  22. package/dist/reference/consistency.d.ts +64 -0
  23. package/dist/reference/consistency.js +234 -0
  24. package/dist/reference/curriculum-alignment.d.ts +7 -0
  25. package/dist/reference/curriculum-alignment.js +166 -0
  26. package/dist/reference/equations-validate.d.ts +50 -0
  27. package/dist/reference/equations-validate.js +90 -0
  28. package/dist/reference/equations.mjs +144 -0
  29. package/dist/reference/gate-math.d.ts +41 -0
  30. package/dist/reference/gate-math.js +151 -0
  31. package/dist/reference/katex-options.mjs +91 -0
  32. package/dist/reference/lab-math-scope.d.ts +21 -0
  33. package/dist/reference/lab-math-scope.js +28 -0
  34. package/dist/reference/label.d.ts +11 -0
  35. package/dist/reference/label.js +38 -0
  36. package/dist/reference/lint-ignore.d.ts +32 -0
  37. package/dist/reference/lint-ignore.js +39 -0
  38. package/dist/reference/math-glyphs.mjs +830 -0
  39. package/dist/reference/prose.d.ts +15 -0
  40. package/dist/reference/prose.js +12 -0
  41. package/dist/reference/references.d.ts +17 -0
  42. package/dist/reference/references.js +42 -0
  43. package/dist/reference/registry.d.ts +6 -0
  44. package/dist/reference/registry.js +548 -0
  45. package/dist/reference/rehype-fail-katex-errors.mjs +55 -0
  46. package/dist/reference/remark-citation.mjs +0 -0
  47. package/dist/reference/remark-notation.mjs +753 -0
  48. package/dist/reference/render-lab-math.d.ts +21 -0
  49. package/dist/reference/render-lab-math.js +58 -0
  50. package/dist/reference/types.d.ts +151 -0
  51. package/dist/reference/types.js +0 -0
  52. package/dist/reference/units-vocab.d.ts +26 -0
  53. package/dist/reference/units-vocab.js +119 -0
  54. package/dist/session/PreferenceStore.d.ts +28 -0
  55. package/dist/session/PreferenceStore.js +30 -0
  56. package/dist/session/user-context.d.ts +7 -0
  57. package/dist/session/user-context.js +10 -0
  58. package/dist/session/user.d.ts +21 -0
  59. package/dist/session/user.js +8 -0
  60. package/package.json +53 -0
  61. package/src/analytics/AnalyticsEmitter.ts +23 -0
  62. package/src/compiler/cli.ts +866 -0
  63. package/src/compiler/collections.ts +554 -0
  64. package/src/compiler/course-config.ts +32 -0
  65. package/src/compiler/lesson-derivation.ts +186 -0
  66. package/src/compiler/manifest.ts +601 -0
  67. package/src/compiler/resolution-report.ts +150 -0
  68. package/src/components/CurriculumMap.astro +208 -0
  69. package/src/components/HoverPanel.astro +67 -0
  70. package/src/components/assessments/AssessmentRunner.tsx +320 -0
  71. package/src/components/assessments/AssessmentSet.astro +54 -0
  72. package/src/components/citation/CitationLayer.astro +173 -0
  73. package/src/components/examples/CompactExample.astro +34 -0
  74. package/src/components/examples/CompactExamples.astro +253 -0
  75. package/src/components/hover-panel.ts +273 -0
  76. package/src/components/notation/NotationGlossary.astro +412 -0
  77. package/src/components/notation/NotationLayer.astro +594 -0
  78. package/src/components/notation/NotationMath.astro +24 -0
  79. package/src/components/notation/SafeMathNode.astro +23 -0
  80. package/src/components/notation/render-notation-math.ts +223 -0
  81. package/src/components/starlight/EquationEnhancer.astro +72 -0
  82. package/src/components/starlight/LayoutHeader.astro +102 -0
  83. package/src/components/starlight/LayoutPageSidebar.astro +33 -0
  84. package/src/components/starlight/LayoutSidebar.astro +29 -0
  85. package/src/components/starlight/LessonFooter.astro +58 -0
  86. package/src/content-config.ts +259 -0
  87. package/src/curriculum/validation.ts +481 -0
  88. package/src/progress/ProgressRepository.ts +64 -0
  89. package/src/reference/base-library.mjs +27 -0
  90. package/src/reference/citation-format.mjs +133 -0
  91. package/src/reference/consistency.ts +375 -0
  92. package/src/reference/curriculum-alignment.ts +228 -0
  93. package/src/reference/equations-validate.ts +167 -0
  94. package/src/reference/equations.mjs +144 -0
  95. package/src/reference/gate-math.ts +250 -0
  96. package/src/reference/katex-options.mjs +91 -0
  97. package/src/reference/lab-math-scope.ts +54 -0
  98. package/src/reference/label.ts +47 -0
  99. package/src/reference/lint-ignore.ts +75 -0
  100. package/src/reference/math-glyphs.mjs +830 -0
  101. package/src/reference/prose.ts +21 -0
  102. package/src/reference/references.ts +82 -0
  103. package/src/reference/registry.ts +723 -0
  104. package/src/reference/rehype-fail-katex-errors.mjs +55 -0
  105. package/src/reference/remark-citation.mjs +0 -0
  106. package/src/reference/remark-notation.mjs +753 -0
  107. package/src/reference/render-lab-math.ts +99 -0
  108. package/src/reference/types.ts +182 -0
  109. package/src/reference/units-vocab.ts +143 -0
  110. package/src/session/PreferenceStore.ts +54 -0
  111. package/src/session/user-context.ts +18 -0
  112. package/src/session/user.ts +24 -0
  113. package/src/styles/global.css +763 -0
@@ -0,0 +1,412 @@
1
+ ---
2
+ import { getCollection, render } from 'astro:content';
3
+ import NotationMath from './NotationMath.astro';
4
+ import NotationLayer, {
5
+ type NotationLayerDefinition,
6
+ } from './NotationLayer.astro';
7
+ import { resolveLabel } from '../../reference/label';
8
+ import {
9
+ isLessonSlug,
10
+ lessonIdFromSlug,
11
+ } from '../../compiler/lesson-derivation';
12
+ import { loadManifest } from '../../compiler/manifest';
13
+
14
+ const [notationEntries, lessonEntries, manifest] = await Promise.all([
15
+ getCollection('notation'),
16
+ getCollection('docs', ({ id }) => isLessonSlug(id)),
17
+ loadManifest(),
18
+ ]);
19
+ const labelOf = (data: { key: string; label?: string }) =>
20
+ resolveLabel(data.key, data.label);
21
+ const notationLabelByKey = new Map(
22
+ notationEntries.map((entry) => [entry.data.key, labelOf(entry.data)]),
23
+ );
24
+
25
+ // "Used directly in" comes from the manifest's resolved backlinks — each
26
+ // lesson's direct `[[key]]` / `\explain` references — not an authored import
27
+ // list (`notation.uses` is retired) and not a page-time registry rebuild.
28
+ const lessonById = new Map(
29
+ lessonEntries.map((lesson) => [lessonIdFromSlug(lesson.id), lesson]),
30
+ );
31
+ const lessonsByNotationKey = new Map<string, typeof lessonEntries>();
32
+ for (const backlink of manifest.notation.backlinks) {
33
+ if (!backlink.direct) continue;
34
+ const lesson = lessonById.get(backlink.lessonId);
35
+ if (!lesson) continue;
36
+ const current = lessonsByNotationKey.get(backlink.key) ?? [];
37
+ if (!current.includes(lesson)) current.push(lesson);
38
+ lessonsByNotationKey.set(backlink.key, current);
39
+ }
40
+
41
+ const renderedEntries = await Promise.all(
42
+ notationEntries
43
+ .sort(
44
+ (left, right) =>
45
+ left.data.domain.localeCompare(right.data.domain) ||
46
+ labelOf(left.data).localeCompare(labelOf(right.data)),
47
+ )
48
+ .map(async (entry) => ({
49
+ entry,
50
+ Content: (await render(entry)).Content,
51
+ })),
52
+ );
53
+
54
+ // Drives the hover explanation for `[[key]]` links inside the definition
55
+ // bodies. Every entry is already shown in full on this page, so the layer
56
+ // renders in panel-only mode: no visible list, just the shared panel.
57
+ const panelDefinitions: NotationLayerDefinition[] = notationEntries.map(
58
+ (entry) => ({
59
+ key: entry.data.key,
60
+ notation: entry.data.latex,
61
+ label: labelOf(entry.data),
62
+ summary: entry.data.meaning,
63
+ units: entry.data.units,
64
+ seeAlso: entry.data.seeAlso,
65
+ editorialStatus: entry.data.editorialStatus,
66
+ scope: 'shared',
67
+ href: `#notation-${entry.data.key}`,
68
+ }),
69
+ );
70
+ ---
71
+
72
+ <p class="glossary-intro">
73
+ These definitions are generated from the shared notation collection. Every
74
+ entry is still <strong>draft</strong> until its wording, convention, source locator,
75
+ and curriculum owner receive human review.
76
+ </p>
77
+
78
+ <nav class="glossary-index" aria-label="Notation glossary index">
79
+ <ul>
80
+ {
81
+ renderedEntries.map(({ entry }) => (
82
+ <li>
83
+ <a href={`#notation-${entry.data.key}`}>{labelOf(entry.data)}</a>
84
+ </li>
85
+ ))
86
+ }
87
+ </ul>
88
+ </nav>
89
+
90
+ <div class="notation-glossary">
91
+ {
92
+ renderedEntries.map(({ entry, Content }) => {
93
+ const backlinks = (lessonsByNotationKey.get(entry.data.key) ?? []).sort(
94
+ (left, right) => left.data.title.localeCompare(right.data.title),
95
+ );
96
+ return (
97
+ <article id={`notation-${entry.data.key}`}>
98
+ <header>
99
+ <span
100
+ class="glossary-symbol-trigger"
101
+ data-notation-key={entry.data.key}
102
+ >
103
+ <NotationMath class="glossary-symbol" latex={entry.data.latex} />
104
+ </span>
105
+ <div>
106
+ <h2>{labelOf(entry.data)}</h2>
107
+ <code>{entry.data.key}</code>
108
+ <a
109
+ class="glossary-anchor"
110
+ href={`#notation-${entry.data.key}`}
111
+ data-glossary-anchor
112
+ aria-label={`Copy link to “${labelOf(entry.data)}”`}
113
+ title="Copy link to this entry"
114
+ >
115
+ <span aria-hidden="true">#</span>
116
+ </a>
117
+ </div>
118
+ <span class="glossary-status">{entry.data.editorialStatus}</span>
119
+ </header>
120
+
121
+ <dl class="glossary-metadata">
122
+ <div>
123
+ <dt>Domain</dt>
124
+ <dd>{entry.data.domain}</dd>
125
+ </div>
126
+ {entry.data.units && (
127
+ <div>
128
+ <dt>Units</dt>
129
+ <dd>{entry.data.units}</dd>
130
+ </div>
131
+ )}
132
+ {entry.data.aliases.length > 0 && (
133
+ <div>
134
+ <dt>Aliases</dt>
135
+ <dd>{entry.data.aliases.join(', ')}</dd>
136
+ </div>
137
+ )}
138
+ {entry.data.seeAlso.length > 0 && (
139
+ <div>
140
+ <dt>See also</dt>
141
+ <dd class="see-also">
142
+ {entry.data.seeAlso.map((key) => (
143
+ <a href={`#notation-${key}`}>
144
+ {notationLabelByKey.get(key) ?? key}
145
+ </a>
146
+ ))}
147
+ </dd>
148
+ </div>
149
+ )}
150
+ </dl>
151
+
152
+ <div class="glossary-definition">
153
+ <Content />
154
+ </div>
155
+
156
+ <footer>
157
+ <p>
158
+ <strong>Introduced by:</strong>{' '}
159
+ {entry.data.alignment.kind === 'competency' ? (
160
+ <>
161
+ <code>{entry.data.alignment.introducedByCompetency}</code> in{' '}
162
+ <code>{entry.data.alignment.introducedInLesson}</code>
163
+ </>
164
+ ) : (
165
+ entry.data.alignment.rationale
166
+ )}
167
+ </p>
168
+ {backlinks.length > 0 && (
169
+ <p>
170
+ <strong>Used directly in:</strong>{' '}
171
+ {backlinks.map((lesson, index) => (
172
+ <>
173
+ {index > 0 && ', '}
174
+ <a
175
+ href={`${import.meta.env.BASE_URL.replace(/\/$/, '')}/${lesson.id}/`}
176
+ >
177
+ {lesson.data.title}
178
+ </a>
179
+ </>
180
+ ))}
181
+ </p>
182
+ )}
183
+ {entry.data.sources.length > 0 ? (
184
+ <div class="source-records">
185
+ <strong>Source records:</strong>
186
+ <ul>
187
+ {entry.data.sources.map((source) => {
188
+ const { id, locator } =
189
+ typeof source === 'string'
190
+ ? { id: source, locator: null }
191
+ : source;
192
+ return (
193
+ <li>
194
+ <code>{id}</code>
195
+ {locator && <span> — {locator}</span>}
196
+ </li>
197
+ );
198
+ })}
199
+ </ul>
200
+ </div>
201
+ ) : (
202
+ <p>
203
+ <strong>Source records:</strong> None declared.
204
+ </p>
205
+ )}
206
+ </footer>
207
+ </article>
208
+ );
209
+ })
210
+ }
211
+ </div>
212
+
213
+ <NotationLayer
214
+ presentation="panel"
215
+ id="glossary-notation"
216
+ definitions={panelDefinitions}
217
+ />
218
+
219
+ <script>
220
+ const RESET_MS = 1200;
221
+
222
+ for (const anchor of document.querySelectorAll<HTMLAnchorElement>(
223
+ '[data-glossary-anchor]',
224
+ )) {
225
+ anchor.addEventListener('click', () => {
226
+ const href = anchor.getAttribute('href');
227
+ if (!href || !navigator.clipboard) return;
228
+
229
+ // Keep the native hash navigation (scroll + history) and copy the
230
+ // absolute URL alongside it.
231
+ const url = new URL(href, window.location.href).href;
232
+ navigator.clipboard.writeText(url).then(
233
+ () => {
234
+ anchor.dataset.copied = 'true';
235
+ window.setTimeout(() => {
236
+ delete anchor.dataset.copied;
237
+ }, RESET_MS);
238
+ },
239
+ () => {
240
+ /* Clipboard blocked: the plain anchor link still works. */
241
+ },
242
+ );
243
+ });
244
+ }
245
+ </script>
246
+
247
+ <style>
248
+ .glossary-index ul {
249
+ display: grid;
250
+ grid-template-columns: repeat(auto-fit, minmax(13rem, 1fr));
251
+ gap: 0.35rem 1rem;
252
+ padding-inline-start: 1.25rem;
253
+ }
254
+
255
+ .notation-glossary {
256
+ display: grid;
257
+ gap: 1.5rem;
258
+ margin-block-start: 2rem;
259
+ }
260
+
261
+ article {
262
+ scroll-margin-block-start: 5rem;
263
+ padding: 1.25rem;
264
+ border: 1px solid var(--sl-color-gray-5);
265
+ border-radius: 0.7rem;
266
+ background: var(--sl-color-gray-7);
267
+ }
268
+
269
+ article > header {
270
+ display: grid;
271
+ grid-template-columns: minmax(4rem, auto) 1fr auto;
272
+ align-items: center;
273
+ gap: 1rem;
274
+ }
275
+
276
+ article h2 {
277
+ margin: 0;
278
+ font-size: var(--sl-text-xl);
279
+ }
280
+
281
+ .glossary-symbol {
282
+ color: var(--sl-color-accent-high);
283
+ font-size: 1.2rem;
284
+ }
285
+
286
+ .glossary-symbol-trigger {
287
+ justify-self: start;
288
+ border-radius: 0.16em;
289
+ cursor: help;
290
+ }
291
+
292
+ .glossary-symbol-trigger:hover,
293
+ .glossary-symbol-trigger:global(.is-notation-active) {
294
+ background: var(--sl-color-accent-low);
295
+ box-shadow: 0 0 0 0.12em var(--sl-color-accent-low);
296
+ }
297
+
298
+ @media (forced-colors: active) {
299
+ .glossary-symbol-trigger:hover,
300
+ .glossary-symbol-trigger:global(.is-notation-active) {
301
+ outline: 2px solid Highlight;
302
+ color: HighlightText;
303
+ background: Highlight;
304
+ box-shadow: none;
305
+ }
306
+ }
307
+
308
+ .glossary-anchor {
309
+ position: relative;
310
+ margin-inline-start: 0.5rem;
311
+ padding-inline: 0.2rem;
312
+ color: var(--sl-color-gray-3);
313
+ font-family: monospace;
314
+ text-decoration: none;
315
+ vertical-align: baseline;
316
+ }
317
+
318
+ .glossary-anchor:hover,
319
+ .glossary-anchor:focus-visible {
320
+ color: var(--sl-color-text-accent);
321
+ }
322
+
323
+ .glossary-anchor[data-copied]::after {
324
+ content: 'Link copied';
325
+ position: absolute;
326
+ inset-block-end: calc(100% + 0.35rem);
327
+ inset-inline-start: 50%;
328
+ translate: -50% 0;
329
+ padding: 0.15rem 0.45rem;
330
+ border-radius: 0.35rem;
331
+ background: var(--sl-color-accent-low);
332
+ color: var(--sl-color-text-accent);
333
+ font-family: var(--sl-font, sans-serif);
334
+ font-size: var(--sl-text-xs);
335
+ white-space: nowrap;
336
+ pointer-events: none;
337
+ }
338
+
339
+ .glossary-status {
340
+ padding: 0.15rem 0.5rem;
341
+ border: 1px solid var(--sl-color-gray-5);
342
+ border-radius: 999px;
343
+ color: var(--sl-color-gray-3);
344
+ font-size: var(--sl-text-xs);
345
+ }
346
+
347
+ .glossary-metadata {
348
+ display: grid;
349
+ gap: 0.45rem;
350
+ margin-block: 1rem;
351
+ }
352
+
353
+ .glossary-metadata div {
354
+ display: grid;
355
+ grid-template-columns: minmax(6rem, 0.3fr) 1fr;
356
+ gap: 0.75rem;
357
+ }
358
+
359
+ .glossary-metadata dt {
360
+ font-weight: 600;
361
+ }
362
+
363
+ .glossary-metadata dd {
364
+ margin: 0;
365
+ }
366
+
367
+ .see-also {
368
+ display: flex;
369
+ flex-wrap: wrap;
370
+ gap: 0.3rem 0.8rem;
371
+ }
372
+
373
+ article > footer {
374
+ margin-block-start: 1rem;
375
+ padding-block-start: 0.75rem;
376
+ border-top: 1px solid var(--sl-color-gray-5);
377
+ color: var(--sl-color-gray-2);
378
+ font-size: var(--sl-text-sm);
379
+ }
380
+
381
+ article > footer p {
382
+ margin-block: 0.4rem;
383
+ }
384
+
385
+ article > footer code + code {
386
+ margin-inline-start: 0.35rem;
387
+ }
388
+
389
+ .source-records {
390
+ margin-block: 0.4rem;
391
+ }
392
+
393
+ .source-records ul {
394
+ margin-block: 0.2rem 0;
395
+ padding-inline-start: 1.2rem;
396
+ }
397
+
398
+ .source-records li {
399
+ margin-block: 0.15rem;
400
+ }
401
+
402
+ @media (max-width: 36rem) {
403
+ article > header {
404
+ grid-template-columns: 1fr;
405
+ }
406
+
407
+ .glossary-metadata div {
408
+ grid-template-columns: 1fr;
409
+ gap: 0.1rem;
410
+ }
411
+ }
412
+ </style>