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.
Files changed (150) hide show
  1. package/CHANGELOG.md +382 -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 +88 -1
  10. package/dist/compiler/course-config.js +35 -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 +98 -6
  14. package/dist/compiler/manifest.js +154 -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-heading-length.d.ts +12 -0
  31. package/dist/reference/gate-heading-length.js +79 -0
  32. package/dist/reference/gate-math.d.ts +32 -5
  33. package/dist/reference/gate-math.js +6 -2
  34. package/dist/reference/gate-prose-notation.d.ts +60 -0
  35. package/dist/reference/gate-prose-notation.js +125 -0
  36. package/dist/reference/keyed-float-validate.d.ts +5 -5
  37. package/dist/reference/keyed-jsx-floats.d.mts +7 -12
  38. package/dist/reference/keyed-jsx-floats.mjs +50 -25
  39. package/dist/reference/knowledge-checks-validate.d.ts +35 -0
  40. package/dist/reference/knowledge-checks-validate.js +78 -0
  41. package/dist/reference/knowledge-checks.d.mts +25 -0
  42. package/dist/reference/knowledge-checks.mjs +45 -0
  43. package/dist/reference/lab-math-scope.d.ts +9 -1
  44. package/dist/reference/lab-math-scope.js +32 -4
  45. package/dist/reference/math-glyphs.d.mts +42 -0
  46. package/dist/reference/math-glyphs.mjs +548 -40
  47. package/dist/reference/mdx-attributes.d.mts +13 -0
  48. package/dist/reference/mdx-attributes.mjs +38 -0
  49. package/dist/reference/mixed-content.d.ts +17 -0
  50. package/dist/reference/mixed-content.js +29 -0
  51. package/dist/reference/page-notation.d.ts +52 -0
  52. package/dist/reference/page-notation.js +171 -0
  53. package/dist/reference/raw-floats-validate.d.ts +18 -0
  54. package/dist/reference/raw-floats-validate.js +53 -0
  55. package/dist/reference/raw-floats.d.mts +18 -0
  56. package/dist/reference/raw-floats.mjs +129 -0
  57. package/dist/reference/rehype-heading-anchors.d.mts +1 -1
  58. package/dist/reference/rehype-heading-anchors.mjs +107 -1
  59. package/dist/reference/rehype-table-header-labels.d.mts +26 -0
  60. package/dist/reference/rehype-table-header-labels.mjs +152 -0
  61. package/dist/reference/remark-abbreviations.d.mts +7 -0
  62. package/dist/reference/remark-abbreviations.mjs +174 -0
  63. package/dist/reference/remark-citation.d.mts +5 -1
  64. package/dist/reference/remark-citation.mjs +0 -0
  65. package/dist/reference/remark-memo.d.mts +16 -0
  66. package/dist/reference/remark-memo.mjs +339 -0
  67. package/dist/reference/remark-notation.d.mts +57 -9
  68. package/dist/reference/remark-notation.mjs +354 -42
  69. package/dist/reference/remark-short-note.d.mts +4 -0
  70. package/dist/reference/remark-short-note.mjs +237 -0
  71. package/dist/reference/types.d.ts +16 -1
  72. package/dist/reference/units-vocab.js +4 -0
  73. package/docs/VERSIONING.md +53 -4
  74. package/docs/authoring/README.md +10 -0
  75. package/docs/authoring/agents.md +17 -2
  76. package/docs/authoring/components.md +138 -0
  77. package/docs/authoring/migrating.md +275 -7
  78. package/docs/authoring/notation.md +615 -1
  79. package/docs/authoring/structure-rules.md +69 -0
  80. package/docs/authoring/structure.md +120 -8
  81. package/docs/authoring/visualizations.md +36 -3
  82. package/package.json +5 -2
  83. package/src/compiler/astro.ts +208 -3
  84. package/src/compiler/cli.ts +29 -2
  85. package/src/compiler/collections.ts +45 -4
  86. package/src/compiler/course-config.ts +132 -0
  87. package/src/compiler/exports/standalone-html.ts +963 -0
  88. package/src/compiler/manifest.ts +281 -22
  89. package/src/components/HoverPanel.astro +45 -4
  90. package/src/components/abbreviation/AbbreviationGlossary.astro +70 -0
  91. package/src/components/abbreviation/AbbreviationLayer.astro +127 -0
  92. package/src/components/assessments/AssessmentRunner.tsx +52 -17
  93. package/src/components/assessments/AssessmentSet.astro +98 -14
  94. package/src/components/assessments/RenderedContent.tsx +55 -0
  95. package/src/components/assessments/assessment-randomization.ts +16 -0
  96. package/src/components/diagram/Diagram.astro +409 -103
  97. package/src/components/disclosure/hover-preview.ts +202 -0
  98. package/src/components/disclosure/persisted-disclosure.ts +66 -0
  99. package/src/components/examples/CompactExamples.astro +2 -0
  100. package/src/components/figure/Figure.astro +279 -10
  101. package/src/components/memo/Memo.astro +453 -0
  102. package/src/components/memo/ShortNote.astro +223 -0
  103. package/src/components/notation/NotationFormula.astro +18 -1
  104. package/src/components/notation/NotationLayer.astro +260 -83
  105. package/src/components/notation/render-notation-math.ts +39 -2
  106. package/src/components/notation/safe-math-to-react.ts +50 -0
  107. package/src/components/reference/FloatRefLayer.astro +233 -15
  108. package/src/components/starlight/CollapsibleSections.astro +461 -0
  109. package/src/components/starlight/DiagramEnhancer.astro +385 -0
  110. package/src/components/starlight/HeadingAnchorEnhancer.astro +5 -17
  111. package/src/components/starlight/KeyedFloatEnhancer.astro +81 -3
  112. package/src/components/starlight/LayoutHeader.astro +50 -0
  113. package/src/components/starlight/LayoutSocialIcons.astro +41 -2
  114. package/src/components/starlight/LessonExportActions.astro +436 -0
  115. package/src/components/starlight/LessonFooter.astro +30 -3
  116. package/src/components/starlight/section-parts.ts +31 -0
  117. package/src/components/table/Table.astro +293 -7
  118. package/src/components/table/TableOverflowExpander.astro +489 -0
  119. package/src/content-config.ts +36 -1
  120. package/src/reference/base-abbreviations.mjs +60 -0
  121. package/src/reference/base-operators.mjs +40 -0
  122. package/src/reference/base-roman-labels.mjs +43 -0
  123. package/src/reference/consistency.ts +73 -2
  124. package/src/reference/diagrams-validate.ts +40 -0
  125. package/src/reference/equations.mjs +67 -14
  126. package/src/reference/gate-abbreviations.ts +128 -0
  127. package/src/reference/gate-heading-length.ts +131 -0
  128. package/src/reference/gate-math.ts +32 -7
  129. package/src/reference/gate-prose-notation.ts +224 -0
  130. package/src/reference/keyed-float-validate.ts +5 -5
  131. package/src/reference/keyed-jsx-floats.mjs +50 -25
  132. package/src/reference/knowledge-checks-validate.ts +130 -0
  133. package/src/reference/knowledge-checks.mjs +45 -0
  134. package/src/reference/lab-math-scope.ts +45 -3
  135. package/src/reference/math-glyphs.mjs +548 -40
  136. package/src/reference/mdx-attributes.mjs +38 -0
  137. package/src/reference/mixed-content.ts +69 -0
  138. package/src/reference/page-notation.ts +252 -0
  139. package/src/reference/raw-floats-validate.ts +103 -0
  140. package/src/reference/raw-floats.mjs +129 -0
  141. package/src/reference/rehype-heading-anchors.mjs +107 -1
  142. package/src/reference/rehype-table-header-labels.mjs +152 -0
  143. package/src/reference/remark-abbreviations.mjs +174 -0
  144. package/src/reference/remark-citation.mjs +0 -0
  145. package/src/reference/remark-memo.mjs +339 -0
  146. package/src/reference/remark-notation.mjs +354 -42
  147. package/src/reference/remark-short-note.mjs +237 -0
  148. package/src/reference/types.ts +16 -0
  149. package/src/reference/units-vocab.ts +4 -0
  150. package/src/styles/global.css +996 -50
package/CHANGELOG.md CHANGED
@@ -14,6 +14,385 @@ under **Breaking** below; a PATCH never breaks.
14
14
 
15
15
  ## [Unreleased]
16
16
 
17
+ ## [0.6.0] - 2026-09-16
18
+
19
+ ### Breaking
20
+
21
+ - **New build-enforced authoring rule S1: no Markdown heading (`##` through
22
+ `######`) and no `<Figure title>` / `<Diagram label>` / `<Memo title>` /
23
+ `<ShortNote title>` may run past 65 characters.** A `<Figure>` with a long
24
+ `caption` and no explicit `title` was silently using the whole caption as
25
+ its always-visible collapsible title-bar text — the reader saw the full
26
+ sentence twice. `gate-heading-length.ts` scans every lesson body; a course
27
+ with an over-limit heading or float title anywhere will now fail
28
+ `pnpm validate:content` / `astro build`. `<Table>` is exempt — it has only
29
+ a `caption`, not a separate `title`. A lesson's own `frontmatter.title`
30
+ (the page `<h1>`) is Review-only, not build-enforced — the gate scans body
31
+ text and cannot see frontmatter. See
32
+ `docs/authoring/structure-rules.md#s1`.
33
+
34
+ - **Removed the "Pin explanation" button from the "Page notation" panel**
35
+ (`.notation-pin`, `[data-notation-control]` — both gone from the DOM). Pin
36
+ a card from the list the same way an inline prose symbol always has: click
37
+ its glyph, now also Tab + Enter/Space-operable (`role="button"`,
38
+ `tabindex="0"`, `aria-pressed`) since it was the button's only remaining
39
+ job. A course's own CSS or tests targeting either selector need updating.
40
+
41
+ - **`NotationLayer.astro`'s separate "Page notation" `<h2>` is gone** —
42
+ the panel had two headings back to back ("Page notation" over "Notation
43
+ used on this page (N)"), saying the same thing twice. The panel's
44
+ `<summary>` is now the only heading, and `.notation-layer-heading` no
45
+ longer exists.
46
+
47
+ - **`Diagram`'s `description` prop and its collapsed "Diagram description"
48
+ fallback block are gone** (`.diagram-fallback` no longer exists in the
49
+ DOM). The caption (`label`) is the diagram's one authored piece of text,
50
+ matching how a LaTeX figure/table caption already tells the whole story —
51
+ a separate always-present description block was inventing a second text
52
+ category most courses never wanted and that only added visible chrome
53
+ under every diagram. Drop any `description="…"` passed to `<Diagram>`;
54
+ the build no longer accepts it (`astro check` fails on an excess prop).
55
+
56
+ ### Added
57
+
58
+ - **`NotationLayer.astro`'s "Page notation" panel and `remark-citation.mjs`'s
59
+ generated "References" section are both properly collapsible now** — same
60
+ native, persisted, deep-linkable disclosure `Figure`/`CollapsibleSections`
61
+ already use, not the always-native-default `<details>` "Page notation" had
62
+ before (no persistence, no course-configurable default, no anchor).
63
+ - `NotationLayer` gains `defaultOpen`/`rememberState` props (mirroring
64
+ `Figure`'s) and a hover-reveal permalink anchor next to its title bar
65
+ (`#page-notation` by default).
66
+ - `remark-citation.mjs`'s generated References heading now carries the
67
+ same `[collapsible]`/`[collapsed]` marker an authored `##` heading uses,
68
+ via a new `defaultOpen` plugin option — so it rides the *same*
69
+ `rehypeHeadingAnchors` / `CollapsibleSections` machinery every other
70
+ heading does, with no engine changes needed on that side. This only
71
+ takes visible effect on a course whose `astro.config.mjs` wires
72
+ `rehypeHeadingAnchors` into its rehype pipeline.
73
+ - New `CourseConfig.disclosureDefaults` (the manifest schema is now v14,
74
+ additive):
75
+ `{ notation?, references? }`, each `true` = open, closed (the engine's
76
+ own default) when omitted or the whole block is absent. Both are
77
+ engine-authored sections, not written by a lesson, so they get their own
78
+ default independent of `defaultCollapsible.sections`/`.subsections` (an
79
+ author's own `##` headings). See `docs/authoring`,
80
+ `playground/astro.config.mjs` for the wiring convention.
81
+
82
+ ### Fixed
83
+
84
+ - **A symbol explained once via `\explain{key}{…}` can now auto-resolve at
85
+ every later bare occurrence of the same declared `latex` on the page — this
86
+ previously failed silently (no tooltip, no build error) for several common
87
+ shapes.** A definition whose whole canonical form is a roman label
88
+ (`\mathrm{SSR}`, `\mathrm{SST}`) never entered the auto-annotator's
89
+ signature index at all, so a bare occurrence of such a symbol could never
90
+ resolve, anywhere on the page, independent of position. Also fixed: a bare
91
+ symbol sitting immediately next to an `\explain{…}{…}` call, a canonical
92
+ match found by seeing through an already-explicit call's own wrapping, and
93
+ a partial `\explain{key}{…}` wrap covering only part of a declared
94
+ multi-token form — all three previously either dropped the symbol silently
95
+ or produced malformed, unparseable output. All of these now resolve
96
+ correctly, or fail loudly as "Unresolved notation" (`resolveMathGlyphs`,
97
+ shared by lesson-body rendering and `gate-math.ts`'s `pnpm validate:content`
98
+ check — so a regression here fails the build immediately rather than
99
+ passing silently through to a render-time surprise). One side effect: the
100
+ fix also restores the `ambiguous-canonical-notation` check for
101
+ `\operatorname{…}`/`\mathrm{…}`-only glyphs, which the same bug had
102
+ silently disabled — a course with two different notation keys declaring
103
+ structurally identical LaTeX (e.g. two keys both `\operatorname{Var}`) will
104
+ now correctly fail `pnpm validate:content` where it previously built with a
105
+ silent, unresolvable ambiguity. See
106
+ [`docs/authoring/notation.md`](docs/authoring/notation.md).
107
+
108
+ ## [0.5.0] - 2026-09-15
109
+
110
+ ### Added
111
+
112
+ - New `createRemarkNotationOptions(manifest, overrides?)` helper
113
+ (`remark-notation.mjs`) returns `{ definitions: manifest.notation.raw,
114
+ manifest, ...overrides }` — the options `remarkNotation` needs, read off one
115
+ manifest in one call, so a course no longer hand-assembles `definitions`,
116
+ `equations`, `tables`, `figures`, and the superseded `chapters` map
117
+ separately (easy to do partially, which is how a page numbered itself under
118
+ the legacy `##`-section scheme while every cross-page reference to it read
119
+ the manifest). `playground/astro.config.mjs`, `README.md`, and
120
+ `src/compiler/cli.ts`'s own `content check` pipeline now wire the plugin
121
+ this way.
122
+
123
+ - New `strict` option on `remarkNotation` (default `false`, so existing wiring
124
+ keeps building unchanged). The existing manifest/number-table cross-check
125
+ only ever caught a disagreement for a *keyed* equation or `<Table>`/
126
+ `<Figure>` — an unkeyed one still silently fell back to legacy
127
+ `##`-section numbering with no diagnostic at all if its lesson had no
128
+ `numbering` placement or cross-page entry. `strict: true` fails the build in
129
+ that case too, for any catalogued lesson with at least one equation,
130
+ `<Table>`, or `<Figure>` (keyed or not). `<Diagram>` is exempt — it already
131
+ renders unnumbered by design when it has no placement.
132
+ `createRemarkNotationOptions(manifest, { strict: true })` is the recommended
133
+ way to turn it on.
134
+
135
+ - New raw/unwrapped-float diagnostics (`src/reference/raw-floats.mjs`,
136
+ `raw-floats-validate.ts`): a raw Markdown table or raw HTML `<table>` never
137
+ wrapped in `<Table>`/`<Figure>`, an `<Diagram>` with no `id`, or an
138
+ `<Diagram>` nested inside anything but `<Memo>`/`<ShortNote>`/`<Table>`/
139
+ `<Figure>` are all invisible to keyed-float validation — they render fine
140
+ but can never get a stable number or `[[…]]` reference. New codes
141
+ `raw-table-markdown`, `raw-table-html`, `diagram-unkeyed`, `diagram-nested`
142
+ surface this in `manifest.diagnostics.rawFloats` (warning-severity,
143
+ aggregated the same way as `tables`/`figures`/`diagrams`/`checks` in
144
+ `cli.ts` and `playground/scripts/compile-manifest.ts`); `remarkNotation`
145
+ reports the same at render time as a defense-in-depth check for content
146
+ added since the manifest was last compiled. See
147
+ [`docs/authoring/structure.md`](docs/authoring/structure.md#raw-and-unwrapped-floats).
148
+
149
+ - Courses can opt into a per-lesson **Print / save as PDF** action with
150
+ `lessonExports.print`. It opens the native browser print dialog—no generated
151
+ PDF artifact or server-side renderer—and prepares a chrome-free, paper-safe
152
+ lesson layout: disclosures, complete tables, diagrams, and assessment items
153
+ are exposed; non-printable controls are removed; linked text stays visibly
154
+ green and remains a real PDF link with canonical destinations; lazy
155
+ interactive plots are hydrated before the print dialog opens; and the reader's
156
+ exact disclosure state is restored after printing without changing saved
157
+ preferences.
158
+
159
+ - Courses can opt into fully functional per-lesson standalone HTML with
160
+ `lessonExports.standaloneHtml` and `canonicalBaseUrl`. The Astro build now
161
+ emits `downloads/<lesson-slug>.html` files that open through `file://`, embed
162
+ the complete style/font/media/module graph, hydrate Astro React islands from
163
+ one shared browser bundle, preserve local lesson interactions, remove site
164
+ chrome, and rewrite deliberate outbound course links to the canonical site.
165
+ Builds fail before publishing an artifact when a render dependency cannot be
166
+ embedded. Downloads retain an offline Light/Dark/Auto theme control. Lesson
167
+ pages expose the download in the top-right header cluster, and the development
168
+ server generates and serves the artifact on demand.
169
+
170
+ - Rule N14: every acronym (`OLS`, `MSE`) or dotted abbreviation (`w.r.t.`,
171
+ `i.i.d.`) a lesson uses in plain prose must now be universal (the shipped
172
+ `base-abbreviations.mjs` baseline plus `abbreviations.allow`/`deny` in
173
+ `course.config.ts`), carry an entry in the new, lighter-weight
174
+ `abbreviations` content collection (`content/abbreviations/*.md` — a name
175
+ resolution only, no formula or units), or already be explained as a math
176
+ quantity via [N8](docs/authoring/notation.md#n8)/[N10](docs/authoring/notation.md#n10).
177
+ Build-enforced (`gate-abbreviations.ts`), same error-severity, no-warm-up
178
+ shape as N9. A new "Abbreviations" section on the glossary page
179
+ (`AbbreviationGlossary.astro`) lists every catalogued entry, and the first
180
+ occurrence of a table-resolved abbreviation on a lesson page is now also an
181
+ inline hover/click card (`remark-abbreviations.mjs` +
182
+ `AbbreviationLayer.astro`), the same card shell the math-notation and
183
+ citation systems use, linking to its glossary entry.
184
+
185
+ ### Fixed
186
+
187
+ - Standalone lesson links no longer return 404 during `astro dev`; the first
188
+ local request now builds the same audited artifact emitted by a static build.
189
+
190
+ - Expanded keyed tables retain their native `<figure>` semantics inside a valid
191
+ landmark wrapper instead of applying an invalid ARIA `region` role directly
192
+ to the figure. Their overflow controls also remain distinguishable from the
193
+ separate row-pagination toggle.
194
+
195
+ - Nested Astro component math can no longer emit a working-looking
196
+ `data-notation-key` without shipping its popup definition. `Diagram` and
197
+ `NotationFormula` register their rendered keys in request-local page scope,
198
+ `labMathScope` accepts component context for lab templates, and the Astro
199
+ integration checks final generated HTML for trigger/payload parity. Known
200
+ component-only shared keys now receive cards without making the entire global
201
+ registry page-wide; unknown keys fail with lesson/component/formula context.
202
+
203
+ ### Changed
204
+
205
+ - Every published engine version must now ship an agent-readable section in
206
+ `docs/authoring/migrating.md`, with exact downstream content/config/script
207
+ steps or an explicit no-action statement. The release checklist and downstream
208
+ agent guide now treat this as part of the release artifact, and a new
209
+ `prepack` guard rejects a version without its matching migration section or
210
+ without the guide included in `package.json#files`.
211
+
212
+ ### Breaking
213
+
214
+ - **The compiler manifest schema is now v13.** It adds
215
+ `diagnostics.rawFloats` (see Added above). The change is additive, but
216
+ consumers that validate `MANIFEST_SCHEMA_VERSION` must accept the new
217
+ version.
218
+
219
+ - **The compiler manifest schema is now v12.** It adds `manifest.abbreviations`
220
+ — the `abbreviations` content collection (N14), flattened to plain
221
+ `{key, abbr, expansion, aliases, domain?}` records sorted by key, so the new
222
+ `remarkAbbreviations` plugin can wrap the first occurrence of a known
223
+ abbreviation in a lesson's prose. The change is additive, but consumers that
224
+ validate `MANIFEST_SCHEMA_VERSION` must accept the new version.
225
+
226
+ - **The compiler manifest schema is now v11.** It adds the normalized
227
+ `manifest.lessonExports` settings used by the build integration and lesson
228
+ UI. The change is additive, but consumers that validate
229
+ `MANIFEST_SCHEMA_VERSION` must accept the new version.
230
+
231
+ - **The compiler manifest schema is now v10.** Numbered knowledge checks add
232
+ `manifest.checks`, `lessons[].knowledgeChecks`, `lessons[].numbering.checks`,
233
+ and `diagnostics.checks`. The change is additive, but consumers that
234
+ validate `MANIFEST_SCHEMA_VERSION` must accept the new version.
235
+
236
+ - **The compiler manifest schema is now v9.** Diagrams are first-class keyed
237
+ floats with their own `manifest.diagrams`, `lessons[].diagrams`,
238
+ `lessons[].numbering.diagrams`, and `diagnostics.diagrams` fields. The
239
+ change is additive, but consumers that validate `MANIFEST_SCHEMA_VERSION`
240
+ must accept the new version.
241
+
242
+ - **New build-enforced authoring rule N9: a physical quantity, unit, or math
243
+ operator must be typeset in `$…$`, never as plain text, anywhere on a
244
+ lesson page (prose or a table cell).** `gate-prose-notation.ts` fails
245
+ `pnpm validate:content` / `astro build` on a numeral glued to an
246
+ abbreviated unit (`45 m/s`), a bare unit in parens (`(s)`, `(m/s²)`), an
247
+ escaped `\|` standing in for an absolute-value bar, a hand-typed `_`/`^`
248
+ sub/superscript, or a `/` between two symbol-like tokens outside a small
249
+ reviewed idiom allowlist. A downstream course with existing plain-text
250
+ notation in a lesson body or table will now fail the build; see
251
+ `docs/authoring/notation.md#n9` for the fix (wrap it in `$…$`, unit
252
+ included — `\text{}`-wrapped and folded into the same span as its symbol,
253
+ e.g. `$t\ \text{(s)}$`) — there is no `lint-ignore.yml` escape hatch for
254
+ this one. The rule does not apply inside a JSX component prop
255
+ (`caption="…"`, `title="…"`), since that text never reaches KaTeX. N9 now
256
+ also scans every assessment item's `prompt` / `explanation` string the same
257
+ way (`gateProseNotationInAssessments`) — same fix, same "no escape hatch."
258
+ - **New `rehype-table-header-labels.mjs` gives an automatic accessible name
259
+ to a table `<th>` that ends up 100% KaTeX (every symbol *and* unit now in
260
+ `$…$` per N9 above, so a bare `$t\ \text{(s)}$` column has no plain DOM
261
+ text at all).** axe-core's `empty-table-header` rule requires real text
262
+ content — not just an `aria-label` (confirmed by reading the rule itself:
263
+ it runs only `has-visible-text`, with none of the
264
+ `aria-label`/`aria-labelledby` fallbacks most other axe name-checking rules
265
+ allow) — so this plugin inserts a visually-hidden `<span class="sr-only">`
266
+ (Starlight's own clip-based utility, not `display:none`) composed from the
267
+ `\explain{key}{…}` key(s) the header's LaTeX names and that key's own
268
+ `label` / `units` notation metadata, already authored for hover cards
269
+ elsewhere. Wired into `playground/astro.config.mjs`'s `rehypePlugins`
270
+ right after `rehypeKatex`. A `\mathrm{…}` compound (`\mathrm{KE}/m`) needs
271
+ each side explicitly `\explain{}`-wrapped for the plugin to have anything
272
+ to read, since the glyph resolver never looks inside a bare `\mathrm{}`
273
+ body — every bare symbol column resolves automatically the same way it
274
+ already did for hover cards.
275
+ - **`AssessmentRunner` now renders `prompt` / `explanation` through the same
276
+ KaTeX pipeline as lesson body math, instead of showing the raw authored
277
+ string.** The gate that checks an assessment math span's glyphs resolve
278
+ (context `assessment` in `gate-math.ts`) is promoted from a Tier-3 warning
279
+ to a build error for the same reason `formula` was at D15: an unresolved
280
+ glyph is now a visible hole in the rendered UI, not an invisible warning. A
281
+ downstream course with an unresolved glyph in assessment math, or
282
+ plain-text notation in an assessment `prompt` / `explanation` (see N9
283
+ above), will now fail the build. Manifest schema is now **v8**:
284
+ `assessmentContent` is new — every assessment item's `prompt` /
285
+ `explanation`, pre-split into text/math segments and resolved against the
286
+ scope of the lesson that embeds it; `AssessmentSet.astro` renders each
287
+ `math` segment via `renderNotationMath` before handing the result to
288
+ `AssessmentRunner`, so `AssessmentRunner`'s `Item` props changed shape:
289
+ `prompt` / `explanation` (`string`) are now `promptContent` /
290
+ `explanationContent` (`MixedContentNode[]`, rendered with the new
291
+ `RenderedContent` component) — a host embedding `AssessmentRunner` directly
292
+ (bypassing `AssessmentSet.astro`) needs to pre-render the same way.
293
+
294
+ - **New build-enforced authoring rule N13: a nonlinear function must take a
295
+ parenthesized argument, even a single symbol — `\sin x` is now a hard
296
+ error, `\sin(x)` is required.** Covers every trigonometric,
297
+ inverse-trigonometric, and hyperbolic function plus `exp`/`log`/`ln`/`lg`
298
+ (the new `PARENTHESIZED_FUNCTIONS` set in `base-operators.mjs`).
299
+ `resolveMathGlyphs` (`math-glyphs.mjs`) walks the parsed KaTeX tree of
300
+ every rendered-math context — lesson body, `notation.formula`, assessment
301
+ `prompt`/`explanation`, a `Diagram` node's `formula`, interactive lab math
302
+ — since all of them already call this one resolver; a violation fails
303
+ `pnpm validate:content` / `astro build` the same way an unresolved glyph
304
+ does, with no `lint-ignore.yml` escape hatch, like N9. `Diagram` and
305
+ `NotationFormula` formulas render through `renderNotationFormula`
306
+ (`render-notation-math.ts`), which previously caught *any*
307
+ `resolveMathGlyphs` failure and degraded to an inert fallback (or, for
308
+ `Diagram`, a generic "could not bind to notation scope" error); it now
309
+ rethrows this specific error instead, so a violation there fails loudly
310
+ with the actual fix rather than a misleading message. A downstream course
311
+ with `\sin\theta`, `\log x`, `\exp t`, etc. anywhere in rendered math will
312
+ now fail the build; see `docs/authoring/notation.md#n13`.
313
+
314
+ - **New build-enforced authoring rule N15: a nested visible delimiter — `(`,
315
+ `[`, `\{`, or `|` inside another one of them — must use its `\left`/`\right`
316
+ sizing form, not the bare character.** The moment a second one of these four
317
+ glyphs sits inside the first — same type or mixed, e.g. `(a + [b])` — every
318
+ level of that nest switches to the sizing form: `\sin(2(90°-\theta))` is now
319
+ a hard error, `\sin\left(2\left(90°-\theta\right)\right)` is required.
320
+ `findNestedBareDelimiters` (`math-glyphs.mjs`) walks the same parsed KaTeX
321
+ tree `resolveMathGlyphs` and N13 already build; a violation fails
322
+ `pnpm validate:content` / `astro build` with no `lint-ignore.yml` escape
323
+ hatch, like N9/N13. A single, non-nested bare delimiter (`(x)`) is
324
+ untouched, and a delimiter inside `\text{…}` (a unit per N9) is unaffected.
325
+ A downstream course with a nested bare delimiter anywhere in rendered math
326
+ will now fail the build; see `docs/authoring/notation.md#n15`.
327
+
328
+ ### Added
329
+
330
+ - **Numbered, referenceable, collapsible knowledge checks.** Assessment ids in
331
+ each lesson's `.checks.yml` now form an independent part-wide number stream.
332
+ `<AssessmentSet>` renders the assessment record's title as the sole heading,
333
+ with a stable `#check-<assessment-id>` permalink; `[[check-<assessment-id>]]`
334
+ and cross-page `[[lesson/slug#check-<assessment-id>]]` references render the
335
+ published number. The check heading reuses the standard section disclosure,
336
+ including persistence, hover peek, deep-link opening, print expansion, and
337
+ the hidden-until-hover expanded chevron. Single-choice answer options are
338
+ Fisher–Yates shuffled in the browser for every full page view while grading
339
+ remains keyed by stable option ids. The manifest schema is now v10, adding
340
+ `checks`, `lessons[].knowledgeChecks`, `lessons[].numbering.checks`, and
341
+ `diagnostics.checks`.
342
+
343
+ - **Detailed memos and inline short notes.** A flow `<Memo>` is now a
344
+ subsection-like optional derivation that can be placed later and cited from
345
+ prose as `[[memo-id]]`; references, the memo's visible `[n]`, and a familiar
346
+ chain-icon permalink beside its title share a URL fragment. Memo blocks
347
+ retain persistent collapse, hover previews,
348
+ deep-link reveal, and print behavior. The new `remark-short-note.mjs` adapter
349
+ and `components/memo/ShortNote.astro` provide the complementary inline form:
350
+ its author-supplied link text expands in place into arbitrary block Markdown
351
+ and restores the intact sentence on collapse. Equations, tables, figures,
352
+ and diagrams inside either detail form remain in their normal numbering
353
+ streams.
354
+
355
+ - New authoring rule N10: a `\mathrm{…}` multi-letter label (`\mathrm{KE}`,
356
+ `\mathrm{PV}`) must be on the shipped/course roman-label baseline
357
+ (`base-roman-labels.mjs`, `course.config.ts` `romanLabels`) or carry a
358
+ notation entry the reader can hover — the `\mathrm{…}` counterpart to N8's
359
+ `operator-explained`, since N3 requires the format but the glyph resolver
360
+ skips a `\mathrm{}` body whole either way, so nothing previously checked
361
+ whether it was ever explained. Surfaced as a `roman-label-explained` Tier-3
362
+ warning by `pnpm validate:content`, same rollout and `lint-ignore.yml`
363
+ shape as N8.
364
+
365
+ ### Added
366
+
367
+ - **Independent diagram numbering and references.** A top-level
368
+ `<Diagram id="…">` is numbered in its own `‹part›.‹n›` stream, renders a
369
+ `#dia-<id>` anchor, and can be cited with `[[dia-<id>]]` (including cross-page
370
+ references). Wide diagrams emit an author-facing build warning above the
371
+ configurable `warnAboveWidth` threshold and gain an unobtrusive horizontal
372
+ expand/shrink control. Expansion moves the original diagram into a non-modal,
373
+ vertically scrollable in-page overlay that follows its document position.
374
+ Diagram titles also minimize/restore the body with an animated chevron,
375
+ permalink icon, and persistent reader state while retaining a complete
376
+ no-JavaScript fallback.
377
+
378
+ ### Fixed
379
+
380
+ - `Diagram` now preserves its build-time SVG dimensions inside its horizontal
381
+ scroller instead of inheriting Starlight's responsive SVG shrinking. This
382
+ keeps the HTML/KaTeX embedded in `foreignObject` nodes aligned with their
383
+ boxes on iOS WebKit (Safari and Chrome), which does not reliably scale mixed
384
+ SVG/HTML content as one unit.
385
+ - Clicking a same-page diagram reference now dismisses its hover card and
386
+ navigates immediately; the card also closes once scrolling has made the
387
+ target diagram fully visible. Preview SVG ids are isolated so cloned arrow
388
+ markers cannot interfere with the original diagram.
389
+ - An expanded diagram title now hides its down-pointing disclosure chevron
390
+ after the interaction, matching collapsible sections and subsections; the
391
+ right-pointing chevron remains visible while the diagram is minimized.
392
+ - Expanded tables and diagrams now remain beneath the sticky site header as
393
+ they scroll upward. Escape now restores an expanded diagram, matching the
394
+ existing expanded-table shortcut.
395
+
17
396
  ## [0.4.0] - 2026-09-09
18
397
 
19
398
  ### Breaking
@@ -165,7 +544,9 @@ under **Breaking** below; a PATCH never breaks.
165
544
  system with its completeness gate, the curriculum-graph validator, the
166
545
  Astro/Starlight UI, the LMS / session / analytics seams, and the `content` CLI.
167
546
 
168
- [Unreleased]: https://github.com/danieltuzes/explico/compare/v0.4.0...HEAD
547
+ [Unreleased]: https://github.com/danieltuzes/explico/compare/v0.6.0...HEAD
548
+ [0.6.0]: https://github.com/danieltuzes/explico/compare/v0.5.0...v0.6.0
549
+ [0.5.0]: https://github.com/danieltuzes/explico/compare/v0.4.0...v0.5.0
169
550
  [0.4.0]: https://github.com/danieltuzes/explico/compare/v0.3.0...v0.4.0
170
551
  [0.3.0]: https://github.com/danieltuzes/explico/compare/v0.2.0...v0.3.0
171
552
  [0.2.0]: https://github.com/danieltuzes/explico/compare/v0.1.1...v0.2.0
package/README.md CHANGED
@@ -71,7 +71,11 @@ export { collections } from 'explico/content-config';
71
71
  // astro.config.mjs
72
72
  import { explicoContentRefresh } from 'explico/compiler/astro.js';
73
73
  import { loadManifest } from 'explico/compiler/manifest.js';
74
- import remarkNotation from 'explico/reference/remark-notation.mjs';
74
+ import remarkMemo from 'explico/reference/remark-memo.mjs';
75
+ import remarkNotation, {
76
+ createRemarkNotationOptions,
77
+ } from 'explico/reference/remark-notation.mjs';
78
+ import remarkShortNote from 'explico/reference/remark-short-note.mjs';
75
79
  import remarkCitation from 'explico/reference/remark-citation.mjs';
76
80
  import { createNotationKatexOptions } from 'explico/reference/katex-options.mjs';
77
81
  import rehypeFailKatexErrors from 'explico/reference/rehype-fail-katex-errors.mjs';
@@ -80,13 +84,15 @@ import { courseConfig } from './content/course.config.ts';
80
84
  const manifest = await loadManifest(courseConfig);
81
85
  // markdown.remarkPlugins: [
82
86
  // remarkMath,
83
- // // One `manifest` carries every numbering input — the equation / table /
84
- // // figure number tables *and* the part each lesson sits in. Pass the
85
- // // tables without the placement and the page numbers its equations by `##`
86
- // // section while references to it read the part scheme, so the same
87
- // // equation reads (2.2) at home and (4.5) abroad; the plugin rejects that
88
- // // at build time rather than shipping it.
89
- // [remarkNotation, { definitions: manifest.notation.raw, manifest }],
87
+ // remarkShortNote,
88
+ // remarkMemo,
89
+ // // `createRemarkNotationOptions` reads every numbering input off one
90
+ // // manifest — the equation / table / figure number tables *and* the part
91
+ // // each lesson sits in — so they can't drift apart. `strict: true` fails
92
+ // // the build when a catalogued lesson has equations/tables/figures but no
93
+ // // numbering or cross-page entry, instead of silently numbering it by
94
+ // // `##` section while references to it read the part scheme.
95
+ // [remarkNotation, createRemarkNotationOptions(manifest, { strict: true })],
90
96
  // [remarkCitation, { sources: manifest.sources }],
91
97
  // ]
92
98
  // … integrations: [explicoContentRefresh(courseConfig, manifest),
@@ -102,6 +108,85 @@ and restarts the development server when any file under `content/` is added,
102
108
  changed, or removed, keeping Markdown transforms and UI components on the same
103
109
  model without an editor-specific task.
104
110
 
111
+ A memo is a substantial optional derivation: it behaves like a collapsible,
112
+ numbered subsection without adding a heading to the page outline. Cite it from
113
+ earlier prose with `[[memo-id]]`, then place the block wherever it best fits.
114
+ The citation, the block's own number, and its chain icon link to the same URL
115
+ fragment:
116
+
117
+ ```mdx
118
+ The omitted calculation is recorded in [[memo-full-check]].
119
+
120
+ Several paragraphs may intervene.
121
+
122
+ <Memo id="full-check" title="Full check" description="The omitted algebra and evidence.">
123
+
124
+ The longer derivation can contain paragraphs, equations, tables, and figures.
125
+
126
+ </Memo>
127
+ ```
128
+
129
+ For a reveal that really belongs inside one sentence, import
130
+ `explico/components/memo/ShortNote.astro` and use `<ShortNote>`. Its `title` is
131
+ the collapsed inline link; expanding replaces that link with the block-capable
132
+ body, then rejoins the sentence when collapsed:
133
+
134
+ ```mdx
135
+ The derivative agrees <ShortNote id="derivative" title="(show how)" description="Show the calculus calculation.">
136
+
137
+ Differentiate the polynomial, set the result to zero, and solve.
138
+
139
+ </ShortNote>. But symmetry is quicker.
140
+ ```
141
+
142
+ Both components require literal `id`, `title`, and `description` strings.
143
+ Memos start collapsed and persist reader state unless `rememberState={false}`;
144
+ short notes always start collapsed.
145
+
146
+ ### Lesson exports
147
+
148
+ A course can opt into a self-contained HTML download and browser-native printing
149
+ for each lesson:
150
+
151
+ ```ts
152
+ export const courseConfig: CourseConfig = {
153
+ // ...the normal course fields
154
+ lessonExports: {
155
+ standaloneHtml: true,
156
+ print: true,
157
+ canonicalBaseUrl: 'https://example.edu/course/',
158
+ },
159
+ };
160
+ ```
161
+
162
+ The existing `explicoContentRefresh(courseConfig, manifest)` integration emits
163
+ `downloads/<lesson-slug>.html` after Astro's static build and adds a download
164
+ control to the top-right header cluster on each lesson. During `astro dev`, the
165
+ first download request runs an isolated static build and streams the resulting
166
+ file; later requests use that result until a watched source file changes. The
167
+ downloaded file contains its CSS,
168
+ KaTeX fonts, images, client scripts, Astro island components, and renderer; it
169
+ opens directly through `file://` without a server or network access. Search,
170
+ sidebars, pagination, and other site chrome are omitted. Same-page references
171
+ stay local, while links that leave the lesson use `canonicalBaseUrl` and
172
+ cross-lesson hover previews degrade to ordinary links. A retained theme control
173
+ lets an offline reader choose Light, Dark, or Auto; Auto follows changes to the
174
+ viewer’s system color scheme.
175
+
176
+ The **Print / save as PDF** action calls the browser's native print dialog; it
177
+ does not download or build a `.pdf` file itself. The print layout removes site
178
+ chrome, expands disclosures and complete data surfaces without changing saved
179
+ reader state, hides controls that have no paper equivalent while retaining their
180
+ current visual result, and keeps linked text clickable with canonical destinations.
181
+ Paper size, margins, browser-added headers/footers, final filename, and the
182
+ choice to save as PDF remain browser and reader settings.
183
+
184
+ Set `canonicalBaseUrl` to the deployed course root whenever retained lesson
185
+ content links elsewhere in the course. If such a link exists without the base,
186
+ the build fails rather than shipping a dead local link. An unresolved or remote
187
+ stylesheet, script, font, image, iframe, media source, worker, WASM file, or CSS
188
+ URL likewise fails the standalone export audit.
189
+
105
190
  ```jsonc
106
191
  // package.json — the read-side CLI
107
192
  "scripts": { "validate:content": "tsx scripts/compile-manifest.ts" }
@@ -135,6 +220,10 @@ version and `content/CHANGELOG.md` move with a content change (area `C`,
135
220
  surfaced to readers in the About `[?]` panel).
136
221
  [`docs/authoring/README.md`](docs/authoring/README.md) is the governance: rule
137
222
  IDs, enforcement levels, and how to amend.
223
+ [`docs/authoring/components.md`](docs/authoring/components.md) is a syntax
224
+ catalog of what a lesson body can contain — native tooltips, tables, figures,
225
+ diagrams, memos, abbreviations, citations — for an author who wants "what's
226
+ available" rather than "why this rule."
138
227
 
139
228
  ## Build
140
229
 
@@ -164,5 +253,11 @@ content — `about.version` in `content/course.config.ts`, tracked in the course
164
253
  For AI authors: [`AGENTS.md`](AGENTS.md) says which changelog and which version a
165
254
  given change updates.
166
255
 
256
+ When upgrading an existing course, use the migration guide shipped with the
257
+ target package at `node_modules/explico/docs/authoring/migrating.md`. Every
258
+ published version has a section, including versions that require no course-side
259
+ changes, so an agent can update content, configuration, and scripts from the
260
+ installed artifact rather than guessing from the changelog.
261
+
167
262
  Status: extracted from the `equations` project; API not yet stable (pre-1.0);
168
263
  the license is undecided (`UNLICENSED` until chosen).
@@ -10,13 +10,21 @@ import '../reference/equations-validate.js';
10
10
  import '../reference/tables-validate.js';
11
11
  import '../reference/keyed-float-validate.js';
12
12
  import '../reference/figures-validate.js';
13
+ import '../reference/diagrams-validate.js';
14
+ import '../reference/knowledge-checks-validate.js';
15
+ import '../reference/raw-floats-validate.js';
13
16
  import '../reference/gate-math.js';
17
+ import '../reference/mixed-content.js';
14
18
  import '../reference/gloss.js';
15
19
  import './knowledge-version.js';
16
20
  import './lesson-derivation.js';
17
21
 
22
+ /** Match only a configured lesson artifact below Astro's deployment base. */
23
+ declare function matchStandaloneDownload(pathname: string, base: string, lessonSlugs: readonly string[]): string | undefined;
18
24
  /** Fail loudly instead of rendering math triggers whose local cards are stale. */
19
25
  declare function assertLocalNotationParity(lessonId: string, authoredKeys: readonly string[], renderedKeys: readonly string[]): void;
26
+ /** Fail an Astro build if final rendered HTML contains an unusable trigger. */
27
+ declare function assertNotationPayloadHtml(page: string, html: string): void;
20
28
  /**
21
29
  * Restart Astro when an authored content file changes. Astro reloads MDX on
22
30
  * its own, but Explico's manifest is created by `astro.config.mjs`; a restart
@@ -27,4 +35,4 @@ declare function assertLocalNotationParity(lessonId: string, authoredKeys: reado
27
35
  */
28
36
  declare function explicoContentRefresh(courseConfig: CourseConfig, manifest: Manifest): AstroIntegration;
29
37
 
30
- export { assertLocalNotationParity, explicoContentRefresh };
38
+ export { assertLocalNotationParity, assertNotationPayloadHtml, explicoContentRefresh, matchStandaloneDownload };