explico 0.11.0 → 0.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +189 -1
- package/dist/compiler/astro.d.ts +7 -1
- package/dist/compiler/astro.js +5 -2
- package/dist/compiler/cli.d.ts +1 -0
- package/dist/compiler/collections.d.ts +25 -2
- package/dist/compiler/collections.js +37 -4
- package/dist/compiler/exports/standalone-html.d.ts +1 -0
- package/dist/compiler/manifest.d.ts +30 -4
- package/dist/compiler/manifest.js +49 -10
- package/dist/compiler/resolution-report.d.ts +1 -0
- package/dist/reference/base-operators.d.mts +6 -0
- package/dist/reference/base-operators.mjs +6 -8
- package/dist/reference/figure-symbols.d.ts +32 -0
- package/dist/reference/figure-symbols.js +55 -0
- package/dist/reference/gate-non-chapter-pages.d.ts +39 -0
- package/dist/reference/gate-non-chapter-pages.js +156 -0
- package/dist/reference/gate-part-pages.d.ts +3 -1
- package/dist/reference/gate-part-pages.js +2 -1
- package/dist/reference/lab-katex-validate.d.ts +43 -0
- package/dist/reference/lab-katex-validate.js +96 -0
- package/dist/reference/lint-ignore.js +1 -1
- package/dist/reference/page-notation.d.ts +5 -3
- package/dist/reference/page-notation.js +8 -8
- package/dist/reference/raw-floats.mjs +37 -13
- package/dist/reference/registry.js +11 -1
- package/dist/reference/remark-notation.mjs +195 -7
- package/dist/reference/render-lab-math.d.ts +11 -2
- package/dist/reference/render-lab-math.js +2 -2
- package/dist/rules/catalog.js +33 -3
- package/dist/rules/rules.schema.json +22 -4
- package/docs/VERSIONING.md +3 -1
- package/docs/authoring/README.md +2 -1
- package/docs/authoring/components.md +4 -4
- package/docs/authoring/migrating.md +271 -0
- package/docs/authoring/notation.md +4 -1
- package/docs/authoring/structure-rules.md +75 -5
- package/docs/authoring/structure.md +14 -0
- package/docs/authoring/visualizations.md +26 -10
- package/package.json +1 -1
- package/src/compiler/astro.ts +17 -6
- package/src/compiler/collections.ts +67 -9
- package/src/compiler/manifest.ts +89 -13
- package/src/components/CourseContents.astro +6 -0
- package/src/components/PartOverview.astro +6 -0
- package/src/components/abbreviation/AbbreviationGlossary.astro +6 -2
- package/src/components/disclosure/print-as-shown.ts +42 -6
- package/src/components/figure/Figure.astro +6 -1
- package/src/components/figure/figure-state.ts +26 -17
- package/src/components/starlight/LessonFooter.astro +27 -7
- package/src/components/table/Table.astro +1 -1
- package/src/reference/base-operators.mjs +6 -8
- package/src/reference/figure-symbols.ts +117 -0
- package/src/reference/gate-non-chapter-pages.ts +241 -0
- package/src/reference/gate-part-pages.ts +1 -1
- package/src/reference/lab-katex-validate.ts +156 -0
- package/src/reference/lint-ignore.ts +4 -1
- package/src/reference/page-notation.ts +14 -11
- package/src/reference/raw-floats.mjs +37 -13
- package/src/reference/registry.ts +16 -1
- package/src/reference/remark-notation.mjs +195 -7
- package/src/reference/render-lab-math.ts +12 -1
- package/src/rules/catalog.ts +37 -3
- package/src/styles/global.css +57 -38
|
@@ -33,6 +33,277 @@ required config block throws with the section of this file to read.
|
|
|
33
33
|
|
|
34
34
|
---
|
|
35
35
|
|
|
36
|
+
## 0.11.1 → 0.12.0
|
|
37
|
+
|
|
38
|
+
**Course changes may be required.** Four changes can fail a build that
|
|
39
|
+
passed on 0.11.1:
|
|
40
|
+
|
|
41
|
+
- a lab that calls KaTeX itself (rule V3, rule N4; step 2);
|
|
42
|
+
- a caption run typed as text in `setFigureState` (step 3);
|
|
43
|
+
- a numbered float, a lab, an equation label or a notation block on the
|
|
44
|
+
landing page or a part's overview page (rule S10; step 4);
|
|
45
|
+
- a symbol that page shows without introducing it, now that its math binds
|
|
46
|
+
like a chapter's (rule S10, rule N4; step 5).
|
|
47
|
+
|
|
48
|
+
The rest is additive, or a fix a course may clean up after (steps 6–9).
|
|
49
|
+
Nothing in `astro.config.mjs` changes, and no `CourseConfig` field is added
|
|
50
|
+
or removed. The manifest schema is now v25 (step 8).
|
|
51
|
+
|
|
52
|
+
### 1. Update the dependency
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
pnpm add -D explico@0.12.0
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
### 2. A lab never calls KaTeX itself (rule V3, rule N4)
|
|
59
|
+
|
|
60
|
+
`pnpm validate:content` now follows each lesson's relative imports into the
|
|
61
|
+
course's components and fails, as `lab-katex-direct`, a file that imports
|
|
62
|
+
`katex` (a type-only import and `katex/dist/katex.min.css` are fine). It
|
|
63
|
+
names the chain of imports that reaches the file. `astro build` then fails
|
|
64
|
+
any letter KaTeX drew inside a `<Figure>` outside a notation binding
|
|
65
|
+
(`figure-symbols.ts`). Upright text (`\text{}`, `\mathrm{}`, `\sin`) is
|
|
66
|
+
exempt. Find the offenders:
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
rg -n "from ['\"]katex['\"]|require\(['\"]katex['\"]\)" content src
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
For each helper that renders math with `katex.renderToString` (an
|
|
73
|
+
`InlineMath`, an SVG label, a legend), render the math at build time in the
|
|
74
|
+
lab's `.astro` wrapper instead, and hand it to the island as a prop:
|
|
75
|
+
|
|
76
|
+
```astro
|
|
77
|
+
---
|
|
78
|
+
import { labMathScope } from 'explico/reference/lab-math-scope';
|
|
79
|
+
import { renderLabMath, type RenderedLabMath } from 'explico/reference/render-lab-math';
|
|
80
|
+
|
|
81
|
+
const entry = Astro.locals.starlightRoute.entry;
|
|
82
|
+
const scope = await labMathScope(
|
|
83
|
+
(entry.data as { notation?: { local?: Array<{ key: string; latex: string }> } }).notation,
|
|
84
|
+
`${entry.body ?? ''}\n${Object.values(MY_LAB_MATH).join('\n')}`,
|
|
85
|
+
{ locals: Astro.locals, context: 'MyLab symbols' },
|
|
86
|
+
);
|
|
87
|
+
const symbols: Record<string, RenderedLabMath> = {};
|
|
88
|
+
for (const [id, template] of Object.entries(MY_LAB_MATH)) {
|
|
89
|
+
symbols[id] = renderLabMath(template, scope, { display: false });
|
|
90
|
+
}
|
|
91
|
+
---
|
|
92
|
+
<MyLabPlot client:visible symbols={symbols} />
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
`{ display: false }` is new in this release. It renders inline math for a
|
|
96
|
+
legend entry, an axis title or a control label, and it never scrolls. The
|
|
97
|
+
island then places the pre-rendered HTML where it drew text before.
|
|
98
|
+
`playground/content/labs/ProjectileRangePicture.astro` and
|
|
99
|
+
`LaunchAnglePicture.astro` in the explico repository are complete examples,
|
|
100
|
+
and `node_modules/explico/docs/authoring/visualizations.md#v3` is the rule.
|
|
101
|
+
|
|
102
|
+
If the course has browser tests, add the same check for what an island draws
|
|
103
|
+
only in the browser:
|
|
104
|
+
|
|
105
|
+
```ts
|
|
106
|
+
import { unboundFigureSymbols } from 'explico/reference/figure-symbols';
|
|
107
|
+
expect(unboundFigureSymbols(await figure.evaluate((el) => el.outerHTML))).toEqual([]);
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
### 3. `setFigureState` runs: a string or `{ math }`
|
|
111
|
+
|
|
112
|
+
```bash
|
|
113
|
+
rg -n "setFigureState|italic:\s*true|\{\s*sub:" content src
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
The `{ text, italic: true }`, `{ text }` and `{ sub }` runs are gone. One
|
|
117
|
+
passed anyway throws, naming the fix, and `astro check` fails it, since
|
|
118
|
+
`FigureStateRun` is now `string | { math: RenderedLabMath }`. Write plain
|
|
119
|
+
text as a string. Write a symbol as `{ math: symbols.launchSpeed }`, rendered
|
|
120
|
+
as in step 2 with `renderLabMath(…, { display: false })`. The caption's
|
|
121
|
+
symbol then opens its card. `ProjectileRangeExplorer.tsx` in the playground
|
|
122
|
+
shows the finished clause.
|
|
123
|
+
|
|
124
|
+
### 4. A page that is not a chapter numbers nothing (rule S10)
|
|
125
|
+
|
|
126
|
+
This covers `content/docs/index.mdx`, every `content/docs/<part>/index.mdx`,
|
|
127
|
+
and any other page outside a part directory. `pnpm validate:content` fails
|
|
128
|
+
each of the following there. The rule is locked, so it cannot be turned off
|
|
129
|
+
or waived.
|
|
130
|
+
|
|
131
|
+
```bash
|
|
132
|
+
rg -n "<(Table|Figure|Diagram|CompactExamples|AssessmentSet)[\s/>]|client:|\\\\label\{eq:" \
|
|
133
|
+
content/docs/*.mdx content/docs/*/index.mdx
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
| Diagnostic | What it found | The edit |
|
|
137
|
+
| --- | --- | --- |
|
|
138
|
+
| `non-chapter-float` | `<Table>`, `<Figure>`, `<Diagram>`, `<CompactExamples>`, `<AssessmentSet>` | Move it into the chapter it belongs to, and link it from here with `[[<chapter>#tab-<key>]]` (`fig-`, `dia-`, `ex-`, `check-`). The link's card previews it. A `<Diagram>` rendered here unnumbered on 0.11.1. |
|
|
139
|
+
| `non-chapter-float` | a component imported from a `labs/` module, or any `client:*` island | Move it into a chapter's `<Figure>` and link that figure. |
|
|
140
|
+
| `non-chapter-float` | `\label{eq:…}` | Drop the label (the equation shows unnumbered), or link the chapter's equation with `[[<chapter>#eq-<key>]]`. |
|
|
141
|
+
| `non-chapter-local-notation` | `notation.local` or `notation.suspend` in the frontmatter | Move the entry to the chapter that introduces the symbol, or to shared notation (`content/notation/`), and name it with `[[key]]`. |
|
|
142
|
+
| `non-chapter-plain-text` | `$…$` or `[[…]]` in `hero.title`, `hero.tagline` or a `<PartOverview chain>` step | Write words. These strings are plain text and print markup literally; a symbol with its card goes in the page body. |
|
|
143
|
+
|
|
144
|
+
A plain Markdown table has no caption and no number, so it stays allowed.
|
|
145
|
+
|
|
146
|
+
### 5. Math on such a page binds like a chapter's (rule S10, rule N4)
|
|
147
|
+
|
|
148
|
+
The landing page and the overview pages now open cards: `[[key]]`, bound
|
|
149
|
+
math, citations, abbreviations and `[[chapter#tab-key]]` links, from the
|
|
150
|
+
page's own notation bundle. That bundle holds shared entries only. The same
|
|
151
|
+
gates as a chapter's now scan these pages: rule N4, rule N8, rule N9,
|
|
152
|
+
rule N10, rule N12, rule N14, rule N22, and the course's `X` rules. Plain
|
|
153
|
+
`$v$` that rendered with no card on 0.11.1 now fails the build. Run
|
|
154
|
+
`pnpm validate:content` and fix each finding as in a chapter:
|
|
155
|
+
|
|
156
|
+
- **An unresolved symbol:** introduce it with `[[key]]` in the prose before
|
|
157
|
+
it, or wrap it in `\explain{key}{…}`. The key must be a shared one.
|
|
158
|
+
- **A quantity, unit or operator in prose (rule N9), a `\mathrm{}` label
|
|
159
|
+
(rule N10), punctuation after display math (rule N12), an abbreviation
|
|
160
|
+
(rule N14), a decoration (rule N22):** fix it as the rule page says.
|
|
161
|
+
A waivable rule can be waived at one place with
|
|
162
|
+
`{/* explico-allow <ID>: <reason> */}` here as in a chapter.
|
|
163
|
+
|
|
164
|
+
Rule S2's "its math and `[[key]]` references render" in a part page's
|
|
165
|
+
`<PartBridge>` and `<PartScope>` now holds. A course that kept symbols off
|
|
166
|
+
its overview pages because `[[key]]` failed the build (data-science-I's
|
|
167
|
+
`tests/source-structure.test.mjs` check, for example) can drop that
|
|
168
|
+
workaround and write its recaps with symbols.
|
|
169
|
+
|
|
170
|
+
### 6. The operator baseline keeps only `\lim` (rule N8)
|
|
171
|
+
|
|
172
|
+
`\limsup`, `\liminf`, `\injlim`, `\projlim`, `\varlimsup`, `\varliminf`,
|
|
173
|
+
`\varinjlim` and `\varprojlim` left `BASE_OPERATORS`. A course that uses one
|
|
174
|
+
now gets rule N8's `operator-explained` warning.
|
|
175
|
+
|
|
176
|
+
```bash
|
|
177
|
+
rg -n '\\(lim(sup|inf)|injlim|projlim|varlim(sup|inf)|varinjlim|varprojlim)\b' content
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
For each hit, give the operator a notation entry, or allow it for the whole
|
|
181
|
+
course in `rules.N8.allow` (`course.settings.json`, or `rules` in
|
|
182
|
+
`course.config.ts`):
|
|
183
|
+
|
|
184
|
+
```json
|
|
185
|
+
"rules": { "N8": { "allow": ["limsup", "liminf"] } }
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
### 7. Clean-ups the fixes allow
|
|
189
|
+
|
|
190
|
+
Each is conditional; no hit means nothing to do.
|
|
191
|
+
|
|
192
|
+
- **Rule S3 waivers for a table in a worked example.** A table inside
|
|
193
|
+
`<CompactExamples>` no longer reports `raw-table-markdown` or
|
|
194
|
+
`raw-table-html`, so a waiver written for it is now reported as
|
|
195
|
+
`waiver-unused`. Delete each
|
|
196
|
+
`{/* explico-allow S3: … */}` that `pnpm validate:content` names.
|
|
197
|
+
- **Link colour on part pages and in `<CourseContents>`.** Links there use
|
|
198
|
+
`--sl-color-text-accent` now. Drop a course rule that set it
|
|
199
|
+
(`rg -n "\.part-overview a|\.course-contents a" src content`).
|
|
200
|
+
- **Workarounds for the menu button under the header on a phone, the
|
|
201
|
+
abbreviation glossary's anchors (`#abbr-…`), a figure's one-pixel vertical
|
|
202
|
+
scrollbar, or a 2 px scroll on a display equation that just fits.** Each
|
|
203
|
+
is fixed in the engine's stylesheet or component; drop the course's own
|
|
204
|
+
rule for it.
|
|
205
|
+
- **A `lint-ignore.yml` line for a kebab-case key.** A `<detail>` may now
|
|
206
|
+
hold a hyphen (`- notation-units:arg-min — …`). A course that shortened or
|
|
207
|
+
dropped such a line to get the file to load can write it as intended.
|
|
208
|
+
- **An `unused-definition` acknowledgement for a `notation.local` entry a
|
|
209
|
+
page uses only by shape** (rule N22's `{}'` for `w'`) is no longer needed.
|
|
210
|
+
The page's own local entries now seed its bundle.
|
|
211
|
+
|
|
212
|
+
### 8. Manifest schema v25, and other code-facing changes
|
|
213
|
+
|
|
214
|
+
Only for course code that reads `build/manifest.json` or calls engine
|
|
215
|
+
helpers directly:
|
|
216
|
+
|
|
217
|
+
- The manifest schema is now v25. A new `pages` array lists every docs page
|
|
218
|
+
that is not a chapter, each with an `id` (`index`, `<part>.index`,
|
|
219
|
+
`glossary`), a `slug`, a `file` and a `notation` bundle of the lesson
|
|
220
|
+
shape. `notation.bundles` and `notation.backlinks` now carry these page
|
|
221
|
+
ids too, so code that looks each id up in `lessons` must skip one it does
|
|
222
|
+
not find. Update any `schemaVersion` assertion to 25.
|
|
223
|
+
- `notationDefinitionsForRenderedPage(manifest, pageId, dependencies)` takes
|
|
224
|
+
a lesson id or a `manifest.pages[].id`. Its errors, and the local-notation
|
|
225
|
+
parity error, are reworded ("on page …", "notation mismatch"). Update a
|
|
226
|
+
test that matched the old text.
|
|
227
|
+
- `.lab-math` no longer scrolls or pads; its `.katex-display` does. A
|
|
228
|
+
float's caption line is at least `1.5rem` tall while it shows a tool
|
|
229
|
+
button.
|
|
230
|
+
|
|
231
|
+
### 9. Things readers see, with no course action
|
|
232
|
+
|
|
233
|
+
A link the author wrote to another chapter now shows that chapter's full
|
|
234
|
+
title as its tooltip (a `title` attribute), unless it has a title of its
|
|
235
|
+
own. An indented `[[key]]` inside a component in an `.mdx` page (a Starlight
|
|
236
|
+
`<Card>`, say) reaches the page's bundle, where 0.11.1 failed the build.
|
|
237
|
+
Printing a page no longer breaks a knowledge check. Re-record screenshots
|
|
238
|
+
that show a caption line, a lab's math, or a chapter link's tooltip.
|
|
239
|
+
|
|
240
|
+
### 10. Verify
|
|
241
|
+
|
|
242
|
+
```bash
|
|
243
|
+
pnpm validate:content # run twice: the first run regenerates build/resolution/*
|
|
244
|
+
pnpm content check --changed
|
|
245
|
+
astro check
|
|
246
|
+
astro build
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
Then run the course's browser tests, if it has any.
|
|
250
|
+
|
|
251
|
+
## 0.11.0 → 0.11.1
|
|
252
|
+
|
|
253
|
+
**No course changes required.** This release has one fix, in the engine's
|
|
254
|
+
stylesheet. A label in the navigation or the "On this page" panel that does
|
|
255
|
+
not fit now wraps onto more lines instead of scrolling sideways, and that
|
|
256
|
+
includes a panel the reader resized. No authoring rule, `CourseConfig` field,
|
|
257
|
+
Astro wiring, course script or CLI command changes. The manifest schema stays
|
|
258
|
+
v24.
|
|
259
|
+
|
|
260
|
+
### 1. Update the dependency
|
|
261
|
+
|
|
262
|
+
```bash
|
|
263
|
+
pnpm add -D explico@0.11.1
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
**Coming from 0.8.0:** follow the "Coming from 0.8.0" note of "0.10.0 →
|
|
267
|
+
0.11.0" below, and install 0.11.1 wherever it says to install 0.11.0.
|
|
268
|
+
|
|
269
|
+
### 2. Only if your course CSS or browser tests reach into the side panels
|
|
270
|
+
|
|
271
|
+
```bash
|
|
272
|
+
rg -n 'data-(navigation|contents)-resized|\.sidebar-pane|\.right-sidebar|layout-(navigation|page-sidebar)-content' src content tests
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
No hit means nothing to do. Otherwise:
|
|
276
|
+
|
|
277
|
+
- **A resized panel's labels** have `white-space: normal` (it was `nowrap`),
|
|
278
|
+
and the panel never scrolls sideways. A test that asserted `nowrap`, or
|
|
279
|
+
`scrollWidth > clientWidth` after narrowing a panel, now asserts that the
|
|
280
|
+
labels wrap and stay inside the panel.
|
|
281
|
+
- **`.sidebar-pane`** has `overflow-x: hidden` at every width. The resized
|
|
282
|
+
"On this page" panel no longer sets `overflow-x: auto` or
|
|
283
|
+
`scrollbar-width: thin`, so it hides its scrollbar, as Starlight does by
|
|
284
|
+
default.
|
|
285
|
+
- **In a resized "On this page" panel**, the column
|
|
286
|
+
(`.right-sidebar-panel .sl-container`) fills the panel. Its width used to
|
|
287
|
+
come from the navigation's `--sl-sidebar-width`. The default,
|
|
288
|
+
shrink-to-fit rail is unchanged.
|
|
289
|
+
- **Word breaking.** Both panels' labels take `overflow-wrap: break-word`
|
|
290
|
+
and `hyphens: auto`, which replaces Starlight's `overflow-wrap: anywhere`
|
|
291
|
+
on the contents links. To break words differently, set these properties
|
|
292
|
+
on `.layout-navigation-content`, `.layout-page-sidebar-content` and
|
|
293
|
+
`.layout-page-sidebar-content a` in the course CSS.
|
|
294
|
+
- **Screenshots** of a narrowed panel need re-recording.
|
|
295
|
+
|
|
296
|
+
### 3. Verify
|
|
297
|
+
|
|
298
|
+
```bash
|
|
299
|
+
pnpm validate:content # run twice: the first run regenerates build/resolution/*
|
|
300
|
+
pnpm content check --changed
|
|
301
|
+
astro check
|
|
302
|
+
astro build
|
|
303
|
+
```
|
|
304
|
+
|
|
305
|
+
Then run the course's browser tests, if it has any.
|
|
306
|
+
|
|
36
307
|
## 0.10.0 → 0.11.0
|
|
37
308
|
|
|
38
309
|
**Course changes are required.** Figures and diagrams no longer compact.
|
|
@@ -225,7 +225,10 @@ validate:content`; `astro build` re-runs the same resolver) fails on an
|
|
|
225
225
|
unresolved glyph (`unresolved-glyph`) in every rendered-math context;
|
|
226
226
|
`consistency.ts` adds the corpus-level lints `glyph-unique-in-corpus`
|
|
227
227
|
(warning), `weak-local`, `gloss-wants-promoting`, `card-wants-demoting`
|
|
228
|
-
(info); a final rendered-trigger/payload parity check backs it at render time
|
|
228
|
+
(info); a final rendered-trigger/payload parity check backs it at render time,
|
|
229
|
+
and a last pass over every built page fails a symbol a figure shows that KaTeX
|
|
230
|
+
drew outside a notation binding (`figure-symbols.ts`: a lab that called KaTeX
|
|
231
|
+
itself — see [V3](visualizations.md#v3)).
|
|
229
232
|
This rule is **locked** — the notation layer is this rule, so a course cannot
|
|
230
233
|
turn it off.
|
|
231
234
|
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
# Structure rules
|
|
2
2
|
|
|
3
3
|
`S` rules govern the shape of a lesson's headings, the short captions on its
|
|
4
|
-
keyed floats, the paragraphs that open a part page,
|
|
4
|
+
keyed floats, the paragraphs that open a part page, what a page that is not
|
|
5
|
+
a chapter may carry, and how a table, a
|
|
5
6
|
diagram, or a convention is declared so the rest of the course can reach it —
|
|
6
7
|
the parts of a page a reader scans, links to, or cites. This
|
|
7
8
|
is not [structure.md](structure.md), which is the taxonomy these rules (and
|
|
@@ -109,7 +110,8 @@ launch angle that gives the largest range.
|
|
|
109
110
|
```
|
|
110
111
|
|
|
111
112
|
Keep a blank line inside each tag, so MDX parses the paragraph as Markdown and
|
|
112
|
-
its math and `[[key]]` references render
|
|
113
|
+
its math and `[[key]]` references render with their cards
|
|
114
|
+
([S10](#s10)). A part page recalls symbols; it defines none.
|
|
113
115
|
|
|
114
116
|
The word range is a course setting, `rules.S2` in `course.settings.json`
|
|
115
117
|
(`CourseConfig.rules`). Each end defaults to 30–150 words; list only what
|
|
@@ -184,9 +186,13 @@ validate:content`) — a warning per lesson (`raw-table-markdown`,
|
|
|
184
186
|
`raw-table-html`); `remarkNotation` repeats it at render time for content
|
|
185
187
|
added since the manifest was compiled. It does not fail the build.
|
|
186
188
|
|
|
187
|
-
_Exceptions._
|
|
188
|
-
|
|
189
|
-
|
|
189
|
+
_Exceptions._ The scan counts a table only where a `<Table>` would number
|
|
190
|
+
it: at the top level of the lesson body, or in a `<Memo>`, `<ShortNote>` or
|
|
191
|
+
`<Fold>`, which keep their place in the reading order. A table already inside
|
|
192
|
+
a `<Table>` or `<Figure>` has its identity. A table in a worked example
|
|
193
|
+
(`<CompactExample>`) or in another component is illustrative, like an
|
|
194
|
+
equation there: leave it a plain Markdown table. A `<Table>` there gets no
|
|
195
|
+
number, and the build fails on it with that reason.
|
|
190
196
|
|
|
191
197
|
## S4
|
|
192
198
|
|
|
@@ -449,3 +455,67 @@ title from one that builds on it.
|
|
|
449
455
|
_Exceptions._ A float that needs no name of its own may go without a
|
|
450
456
|
`short`; the "Figures and tables" page then lists it by its long caption,
|
|
451
457
|
muted.
|
|
458
|
+
|
|
459
|
+
## S10
|
|
460
|
+
|
|
461
|
+
**A page that is not a chapter — the landing page, a part’s overview page —
|
|
462
|
+
explains its symbols like a chapter, but carries no numbered float: it links
|
|
463
|
+
the chapter’s float instead.**
|
|
464
|
+
|
|
465
|
+
This applies to `content/docs/index.mdx`, to every
|
|
466
|
+
`content/docs/<part>/index.mdx`, and to any other page outside a part
|
|
467
|
+
directory. What makes a card works there as in a chapter:
|
|
468
|
+
|
|
469
|
+
- a `[[key]]` term and `\explain{key}{…}` in math, each opening its card;
|
|
470
|
+
- plain math, bound symbol by symbol, so every symbol it shows is explained;
|
|
471
|
+
- a citation `[@id]`, an abbreviation, and a link to a chapter’s float,
|
|
472
|
+
`[[motion/constant-acceleration#tab-kinematics-summary]]`, whose card
|
|
473
|
+
previews the table.
|
|
474
|
+
|
|
475
|
+
The math and prose rules hold there too ([N4](notation.md#n4),
|
|
476
|
+
[N9](notation.md#n9), [N10](notation.md#n10), [N14](notation.md#n14), and a
|
|
477
|
+
course’s `X` rules). What such a page may not carry:
|
|
478
|
+
|
|
479
|
+
| Not here | Instead |
|
|
480
|
+
| --- | --- |
|
|
481
|
+
| `<Table>`, `<Figure>`, `<Diagram>`, `<CompactExamples>`, `<AssessmentSet>` | Link the chapter’s float: `[[<chapter>#tab-<key>]]` (`fig-`, `dia-`, …). |
|
|
482
|
+
| A lab, or any `client:*` island | Link the chapter’s `<Figure>` that holds it. |
|
|
483
|
+
| `\label{eq:…}` on a display equation | Drop the label (the equation shows unnumbered), or link the chapter’s equation. |
|
|
484
|
+
| `notation.local`, `notation.suspend` | Define the symbol in the chapter that introduces it, or in shared notation, and name it with `[[key]]`. |
|
|
485
|
+
| `$…$` or `[[…]]` in `hero.title`, `hero.tagline`, a `<PartOverview chain>` step | Write words; these strings are plain text and print markup literally. |
|
|
486
|
+
|
|
487
|
+
```mdx
|
|
488
|
+
<PartScope>
|
|
489
|
+
|
|
490
|
+
Part 1 ended on a point that moves under a constant acceleration
|
|
491
|
+
[[motion.acceleration]]. The worked examples in
|
|
492
|
+
[[motion/constant-acceleration#tab-kinematics-summary]] give its velocity.
|
|
493
|
+
|
|
494
|
+
</PartScope>
|
|
495
|
+
```
|
|
496
|
+
|
|
497
|
+
A plain Markdown table is ordinary text on such a page: it has no caption and
|
|
498
|
+
no number, so it is not a float.
|
|
499
|
+
|
|
500
|
+
_Rationale._ A float number starts with its chapter’s number, and a page that
|
|
501
|
+
is not a chapter has none: a table on a part page would need a number that
|
|
502
|
+
collides with a chapter’s, or no number, which makes it the one float in the
|
|
503
|
+
course a reference cannot reach. A part page in a printed book points to the
|
|
504
|
+
chapters’ floats and does not copy them. The reference card gives the reader
|
|
505
|
+
the same view without the copy. Symbols are different: an overview that
|
|
506
|
+
recalls a result needs its symbols, and a symbol without its card breaks the
|
|
507
|
+
promise every other page keeps.
|
|
508
|
+
|
|
509
|
+
_How to apply._ Write the overview’s recap with the chapter’s own symbols,
|
|
510
|
+
each introduced with `[[key]]`. Where the recap leans on a table, figure or
|
|
511
|
+
equation, link it instead of repeating it. If a symbol has no shared entry,
|
|
512
|
+
recall it in words or give it one in `content/notation/`.
|
|
513
|
+
|
|
514
|
+
_Enforcement._ Build-enforced (`gate-non-chapter-pages.ts`,
|
|
515
|
+
`pnpm validate:content`): `non-chapter-float` for a numbered block, a lab or
|
|
516
|
+
an equation label; `non-chapter-local-notation` for a notation block;
|
|
517
|
+
`non-chapter-plain-text` for markup in a plain-text string. `astro build`
|
|
518
|
+
fails the same numbered block and equation label at render time. This rule is
|
|
519
|
+
**locked**: turning it off would not give the page a number to print.
|
|
520
|
+
|
|
521
|
+
_Exceptions._ None.
|
|
@@ -154,6 +154,14 @@ missing, holds more than one paragraph, or falls outside the course's
|
|
|
154
154
|
takes no float numbers, and never appears in `manifest.lessons` or a track. A
|
|
155
155
|
course adds one part at a time; a part without one behaves exactly as before.
|
|
156
156
|
|
|
157
|
+
**It explains its symbols, and links its floats**
|
|
158
|
+
([S10](structure-rules.md#s10)). A `[[key]]` term, math, a citation, an
|
|
159
|
+
abbreviation and a `[[chapter#tab-key]]` link each open their card on an
|
|
160
|
+
overview page, as on the landing page and any other page outside a part; the
|
|
161
|
+
manifest lists each such page under `pages` with its notation bundle. A
|
|
162
|
+
`<Table>`, `<Figure>`, `<Diagram>`, lab or `\label{eq:…}` stays in its
|
|
163
|
+
chapter, which alone can number it, and the overview links it.
|
|
164
|
+
|
|
157
165
|
`<PartOverview>` reads the manifest for everything it lists — chapters in
|
|
158
166
|
reading order with their descriptions, the competencies the part teaches, the
|
|
159
167
|
parts it builds on and leads to — so the page cannot fall out of date with the
|
|
@@ -432,6 +440,12 @@ that wants them addressed should wrap the table in `<Table id="…">`/
|
|
|
432
440
|
`<Figure id="…">`, or give the diagram an `id` and move it to the top level (or
|
|
433
441
|
inside `<Memo>`/`<ShortNote>`), or wrap it in `<Figure>`.
|
|
434
442
|
|
|
443
|
+
A raw table counts only where a `<Table>` would number it: at the top level,
|
|
444
|
+
or in a `<Memo>`, `<ShortNote>` or `<Fold>`. One in a worked example
|
|
445
|
+
(`<CompactExample>`) or another component is illustrative, like an equation
|
|
446
|
+
there, and stays a plain table: a `<Table>` or a `<Figure>` there gets no
|
|
447
|
+
number, and the build fails on it, naming the component it sits in.
|
|
448
|
+
|
|
435
449
|
### Knowledge checks
|
|
436
450
|
|
|
437
451
|
An assessment title is authored once, in `content/assessments/<id>.json`, and
|
|
@@ -115,11 +115,15 @@ equations.**
|
|
|
115
115
|
An interactive figure is a React island, hydrated `client:visible` (or
|
|
116
116
|
`client:idle`). It carries:
|
|
117
117
|
|
|
118
|
-
- **Gate-checked math.**
|
|
118
|
+
- **Gate-checked math.** Every symbol the figure shows — an equation, a
|
|
119
|
+
legend entry, an axis title, a slider label — goes through the
|
|
119
120
|
`labMathScope` / `renderLabMath` / `\slot{}` seam so its symbols are resolved
|
|
120
121
|
against the page's real notation at build time and the island only patches
|
|
121
|
-
numbers into pre-rendered KaTeX.
|
|
122
|
-
|
|
122
|
+
numbers into pre-rendered KaTeX. `renderLabMath(template, scope)` renders a
|
|
123
|
+
display equation; `renderLabMath(template, scope, { display: false })`
|
|
124
|
+
renders inline math for a formula that sits in a line, which never scrolls.
|
|
125
|
+
A lab never calls KaTeX itself: its symbols would open no card. A wrapper
|
|
126
|
+
whose template names notation not otherwise present in lesson MDX passes
|
|
123
127
|
`{ locals: Astro.locals, context: '<ComponentName> <template>' }` as the third
|
|
124
128
|
`labMathScope` argument; that is the declaration that ships those definitions
|
|
125
129
|
in this page's popup payload. See
|
|
@@ -136,8 +140,7 @@ An interactive figure is a React island, hydrated `client:visible` (or
|
|
|
136
140
|
that describes the plot ("the dot marks the landing point") belongs in the
|
|
137
141
|
figure's caption.
|
|
138
142
|
- **Picture, then caption, then tools.** A lab is laid out as a LaTeX figure
|
|
139
|
-
with its controls after it
|
|
140
|
-
decision 3). The plot goes in the `<Figure>`'s default slot — its
|
|
143
|
+
with its controls after it. The plot goes in the `<Figure>`'s default slot — its
|
|
141
144
|
picture — and the controls in its `tools` slot: an instruction line, the
|
|
142
145
|
sliders and fields, the live equation. The two are separate islands that
|
|
143
146
|
share their values. The tools render after the caption line, in a tray of
|
|
@@ -151,10 +154,13 @@ An interactive figure is a React island, hydrated `client:visible` (or
|
|
|
151
154
|
case shown — "Shown for v₀ = 20 m/s and θ = 30°: range 35.3 m, time of
|
|
152
155
|
flight 2.04 s." The lab writes it with `setFigureState(element, runs)`
|
|
153
156
|
(`explico/components/figure/figure-state.ts`), `element` being any element
|
|
154
|
-
of its own inside the figure and
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
157
|
+
of its own inside the figure and each run a string of plain text or
|
|
158
|
+
`{ math }`, a symbol rendered at build time with
|
|
159
|
+
`renderLabMath(template, scope, { display: false })` and handed to the
|
|
160
|
+
island — never HTML, and never a symbol typed as text, which would open no
|
|
161
|
+
card. `ProjectileRangePicture.astro` renders the playground's. The clause
|
|
162
|
+
prints with the caption; a reader of the long caption elsewhere (a
|
|
163
|
+
preview card, a fold's bar) sees the caption alone.
|
|
158
164
|
- **Which text goes where.** The caption names the figure and says what it
|
|
159
165
|
shows — its subject, its marks and symbols, the case shown — and may state
|
|
160
166
|
the one thing to notice. The argument drawn from the figure is lesson
|
|
@@ -184,7 +190,17 @@ _How to apply._ Keep the island's job to "patch numbers and redraw". Domain
|
|
|
184
190
|
math lives in a plain module (`playground/content/domain/motion.ts`), not in the
|
|
185
191
|
component.
|
|
186
192
|
|
|
187
|
-
_Enforcement._
|
|
193
|
+
_Enforcement._ Partly build-enforced. `pnpm validate:content` follows each
|
|
194
|
+
lesson's relative imports to the course's components and fails one that
|
|
195
|
+
imports `katex` itself (`lab-katex-validate.ts`, `lab-katex-direct`, an
|
|
196
|
+
error), naming the chain of imports that reaches it. `astro build` then reads
|
|
197
|
+
every figure it rendered and fails a symbol KaTeX drew outside a notation
|
|
198
|
+
binding (`figure-symbols.ts`, rule [N4](notation.md#n4)); a course's browser
|
|
199
|
+
test runs the same `unboundFigureSymbols` on a hydrated figure's
|
|
200
|
+
`outerHTML`, which also holds what an island draws only in the browser, as
|
|
201
|
+
the playground's `labs.spec.ts` does. `setFigureState` takes no symbol typed
|
|
202
|
+
as text, so `astro check` fails an italic or a `sub` run. That a lab labels
|
|
203
|
+
its controls and names nothing of its own is review-only.
|
|
188
204
|
|
|
189
205
|
_Exceptions._ None.
|
|
190
206
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "explico",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.12.0",
|
|
4
4
|
"description": "Build-time engine for prerequisite-aware, notation-rigorous course content: one validated content model, a semantic notation/citation/equation system with a completeness gate, and a deterministic JSON manifest.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "UNLICENSED",
|
package/src/compiler/astro.ts
CHANGED
|
@@ -12,6 +12,7 @@ import {
|
|
|
12
12
|
writeManifest,
|
|
13
13
|
} from './manifest';
|
|
14
14
|
import { missingNotationPayloadKeys } from '../reference/page-notation';
|
|
15
|
+
import { assertFigureSymbolsBound } from '../reference/figure-symbols';
|
|
15
16
|
import { writeStandaloneLessonExports } from './exports/standalone-html';
|
|
16
17
|
import remarkAbbreviations from '../reference/remark-abbreviations.mjs';
|
|
17
18
|
import remarkCitation from '../reference/remark-citation.mjs';
|
|
@@ -99,7 +100,12 @@ async function buildDevelopmentDownloads(
|
|
|
99
100
|
}
|
|
100
101
|
}
|
|
101
102
|
|
|
102
|
-
/**
|
|
103
|
+
/**
|
|
104
|
+
* Fail loudly instead of rendering math triggers whose local cards are
|
|
105
|
+
* missing. The manifest bundles every `notation.local` entry with its page,
|
|
106
|
+
* so the two lists differ only when the manifest is older than the page, or
|
|
107
|
+
* when `validate:content` rejected an entry.
|
|
108
|
+
*/
|
|
103
109
|
export function assertLocalNotationParity(
|
|
104
110
|
lessonId: string,
|
|
105
111
|
authoredKeys: readonly string[],
|
|
@@ -114,10 +120,13 @@ export function assertLocalNotationParity(
|
|
|
114
120
|
return;
|
|
115
121
|
}
|
|
116
122
|
throw new Error(
|
|
117
|
-
`
|
|
118
|
-
`[${authored.join(', ')}], but the
|
|
119
|
-
`[${rendered.join(', ')}].
|
|
120
|
-
`
|
|
123
|
+
`Explico notation mismatch for ${lessonId}: the page's frontmatter ` +
|
|
124
|
+
`notation.local declares [${authored.join(', ')}], but the manifest's ` +
|
|
125
|
+
`notation bundle for the page holds [${rendered.join(', ')}]. The ` +
|
|
126
|
+
`manifest bundles every notation.local entry with its page, so either ` +
|
|
127
|
+
`it is stale — pass courseConfig to loadManifest() and add ` +
|
|
128
|
+
`explicoContentRefresh() to the Astro integrations — or ` +
|
|
129
|
+
`\`pnpm validate:content\` rejected an entry: fix the errors it reports.`,
|
|
121
130
|
);
|
|
122
131
|
}
|
|
123
132
|
|
|
@@ -228,7 +237,9 @@ async function validateGeneratedNotationPayloads(directory: URL): Promise<void>
|
|
|
228
237
|
if (entry.isDirectory()) {
|
|
229
238
|
await walk(child);
|
|
230
239
|
} else if (entry.isFile() && entry.name.endsWith('.html')) {
|
|
231
|
-
|
|
240
|
+
const html = await readFile(child, 'utf8');
|
|
241
|
+
assertNotationPayloadHtml(relative(root, child), html);
|
|
242
|
+
assertFigureSymbolsBound(relative(root, child), html);
|
|
232
243
|
}
|
|
233
244
|
}
|
|
234
245
|
};
|
|
@@ -238,15 +238,21 @@ function sourceSpan(file: string, text?: string, index?: number): SourceSpan {
|
|
|
238
238
|
};
|
|
239
239
|
}
|
|
240
240
|
|
|
241
|
-
|
|
241
|
+
/**
|
|
242
|
+
* Blank code, keeping every newline. An indented line is code only in `.md`:
|
|
243
|
+
* MDX has no indented code block, so text indented inside a component
|
|
244
|
+
* (`<Card>`, `<Memo>`) is prose, and its `[[key]]` renders a term.
|
|
245
|
+
*/
|
|
246
|
+
function maskMarkdownLiterals(text: string, mdx: boolean): string {
|
|
242
247
|
const mask = (value: string) => value.replace(/[^\n]/g, ' ');
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
248
|
+
const fenced = text.replace(
|
|
249
|
+
/(^|\n)[ \t]{0,3}(`{3,}|~{3,})[^\n]*\n[\s\S]*?(?:\n[ \t]{0,3}\2[^\n]*(?=\n|$)|$)/g,
|
|
250
|
+
mask,
|
|
251
|
+
);
|
|
252
|
+
return (mdx ? fenced : fenced.replace(/^(?: {4}|\t).*$/gm, mask)).replace(
|
|
253
|
+
/(`+)(?!`)([^\n]*?)\1/g,
|
|
254
|
+
mask,
|
|
255
|
+
);
|
|
250
256
|
}
|
|
251
257
|
|
|
252
258
|
/**
|
|
@@ -259,7 +265,7 @@ export function extractNotationReferences(
|
|
|
259
265
|
kind: NotationReferenceInput['kind'] | undefined,
|
|
260
266
|
): NotationReferenceInput[] {
|
|
261
267
|
const references: NotationReferenceInput[] = [];
|
|
262
|
-
const searchable = maskMarkdownLiterals(text);
|
|
268
|
+
const searchable = maskMarkdownLiterals(text, file.endsWith('.mdx'));
|
|
263
269
|
const patterns: readonly [RegExp, NotationReferenceInput['kind']][] = [
|
|
264
270
|
[/\[\[\s*([^\][]+?)\s*\]\]/g, kind ?? 'prose'],
|
|
265
271
|
[/\\explain\{([^{}]+)\}/g, kind ?? 'math'],
|
|
@@ -635,6 +641,58 @@ function notationLessonEntries(): NotationLessonInput[] {
|
|
|
635
641
|
return lessons;
|
|
636
642
|
}
|
|
637
643
|
|
|
644
|
+
/**
|
|
645
|
+
* A docs page that is **not a chapter** (rule S10): the landing page, a part's
|
|
646
|
+
* overview page, and any other page outside a part directory — every slug
|
|
647
|
+
* `isLessonSlug` rejects. Such a page may explain its symbols like a chapter,
|
|
648
|
+
* but it recalls them only: it takes no `notation.local` and no float numbers.
|
|
649
|
+
*/
|
|
650
|
+
export interface NonChapterPage {
|
|
651
|
+
/** `lessonIdFromSlug(slug)` — `index`, `motion.index`, `glossary`. Never a lesson id. */
|
|
652
|
+
readonly id: string;
|
|
653
|
+
/** Docs slug without extension, e.g. `motion/index`. */
|
|
654
|
+
readonly slug: string;
|
|
655
|
+
readonly file: string;
|
|
656
|
+
readonly data: Record<string, unknown>;
|
|
657
|
+
readonly body: string;
|
|
658
|
+
readonly bodyLine: number;
|
|
659
|
+
/**
|
|
660
|
+
* The page as notation input — no local entries, no suspensions — so the
|
|
661
|
+
* registry resolves its `[[key]]` references and the text and math gates
|
|
662
|
+
* scan it exactly as they scan a chapter.
|
|
663
|
+
*/
|
|
664
|
+
readonly notation: NotationLessonInput;
|
|
665
|
+
}
|
|
666
|
+
|
|
667
|
+
export function loadNonChapterPages(): NonChapterPage[] {
|
|
668
|
+
const pages: NonChapterPage[] = [];
|
|
669
|
+
for (const path of filesBelow(join(CONTENT_ROOT, 'docs'), ['.md', '.mdx'])) {
|
|
670
|
+
const slug = docSlug(path);
|
|
671
|
+
if (isLessonSlug(slug)) continue;
|
|
672
|
+
const { file, data, body, bodyLine } = parseDocument(path);
|
|
673
|
+
const id = lessonIdFromSlug(slug);
|
|
674
|
+
pages.push({
|
|
675
|
+
id,
|
|
676
|
+
slug: slug.replace(/\.(md|mdx)$/, ''),
|
|
677
|
+
file,
|
|
678
|
+
data,
|
|
679
|
+
body,
|
|
680
|
+
bodyLine,
|
|
681
|
+
notation: {
|
|
682
|
+
lessonId: id,
|
|
683
|
+
status: status(data.editorialStatus ?? 'draft', `${file} editorialStatus`),
|
|
684
|
+
localDefinitions: [],
|
|
685
|
+
suspended: [],
|
|
686
|
+
references: extractNotationReferences(body, file, undefined),
|
|
687
|
+
body,
|
|
688
|
+
bodyLine,
|
|
689
|
+
source: { file },
|
|
690
|
+
},
|
|
691
|
+
});
|
|
692
|
+
}
|
|
693
|
+
return pages;
|
|
694
|
+
}
|
|
695
|
+
|
|
638
696
|
export async function loadNotationRegistryInput(): Promise<NotationRegistryInput> {
|
|
639
697
|
return {
|
|
640
698
|
sharedDefinitions: sharedNotationEntries(),
|