explico 0.13.1 → 0.14.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 +879 -1
- package/README.md +200 -30
- package/dist/compiler/astro.d.ts +15 -2
- package/dist/compiler/astro.js +40 -11
- package/dist/compiler/cli-main.d.ts +43 -0
- package/dist/compiler/cli-main.js +392 -0
- package/dist/compiler/cli.d.ts +424 -9
- package/dist/compiler/cli.js +1289 -8
- package/dist/compiler/collections.d.ts +88 -1
- package/dist/compiler/collections.js +170 -25
- package/dist/compiler/course-config.d.ts +1 -1
- package/dist/compiler/course-config.js +105 -1
- package/dist/compiler/decks.d.ts +161 -0
- package/dist/compiler/decks.js +235 -0
- package/dist/compiler/exports/combined-html.d.ts +5 -1
- package/dist/compiler/exports/deck-fit.d.ts +55 -0
- package/dist/compiler/exports/deck-fit.js +90 -0
- package/dist/compiler/exports/export-chapters.d.ts +37 -6
- package/dist/compiler/exports/export-chapters.js +64 -5
- package/dist/compiler/exports/export-pages.d.ts +30 -3
- package/dist/compiler/exports/export-pages.js +24 -1
- package/dist/compiler/exports/latex/beamerthemeexplico.sty +124 -0
- package/dist/compiler/exports/latex/cache.d.ts +78 -0
- package/dist/compiler/exports/latex/cache.js +129 -0
- package/dist/compiler/exports/latex/capture.d.ts +10 -1
- package/dist/compiler/exports/latex/capture.js +3 -1
- package/dist/compiler/exports/latex/compile.d.ts +13 -2
- package/dist/compiler/exports/latex/compile.js +12 -1
- package/dist/compiler/exports/latex/deck.d.ts +92 -0
- package/dist/compiler/exports/latex/deck.js +349 -0
- package/dist/compiler/exports/latex/document.d.ts +51 -6
- package/dist/compiler/exports/latex/document.js +71 -12
- package/dist/compiler/exports/latex/explico-common.sty +198 -0
- package/dist/compiler/exports/latex/explico.cls +13 -183
- package/dist/compiler/exports/latex/serialize.d.ts +60 -4
- package/dist/compiler/exports/latex/serialize.js +4 -2
- package/dist/compiler/exports/latex-export.d.ts +24 -2
- package/dist/compiler/exports/latex-export.js +107 -26
- package/dist/compiler/exports/markdown/serialize.d.ts +24 -4
- package/dist/compiler/exports/markdown/serialize.js +85 -8
- package/dist/compiler/exports/markdown-export.d.ts +6 -1
- package/dist/compiler/exports/markdown-export.js +50 -11
- package/dist/compiler/exports/standalone-html.d.ts +10 -5
- package/dist/compiler/exports/standalone-html.js +3 -3
- package/dist/compiler/exports/static-tree.d.ts +38 -1
- package/dist/compiler/exports/static-tree.js +120 -4
- package/dist/compiler/knowledge-version.d.ts +26 -2
- package/dist/compiler/knowledge-version.js +27 -17
- package/dist/compiler/lesson-derivation.d.ts +143 -6
- package/dist/compiler/lesson-derivation.js +145 -13
- package/dist/compiler/manifest.d.ts +133 -4
- package/dist/compiler/manifest.js +359 -58
- package/dist/compiler/page-versions.d.ts +160 -0
- package/dist/compiler/page-versions.js +198 -0
- package/dist/compiler/render-diagnostics.d.ts +5 -1
- package/dist/compiler/resolution-report.d.ts +24 -6
- package/dist/compiler/resolution-report.js +22 -4
- package/dist/compiler/validate-content.d.ts +7 -0
- package/dist/compiler/validate-content.js +160 -0
- package/dist/{course-config-rRtME5UV.d.ts → course-config-BLtIdkei.d.ts} +164 -1
- package/dist/curriculum/competency-graph.d.ts +54 -0
- package/dist/curriculum/competency-graph.js +41 -0
- package/dist/reference/base-library.d.mts +8 -0
- package/dist/reference/base-library.mjs +19 -0
- package/dist/reference/consistency.d.ts +8 -4
- package/dist/reference/consistency.js +76 -26
- package/dist/reference/deck-remark.d.mts +123 -0
- package/dist/reference/deck-remark.mjs +600 -0
- package/dist/reference/deck-source.d.ts +107 -0
- package/dist/reference/deck-source.js +140 -0
- package/dist/reference/decks-validate.d.ts +133 -0
- package/dist/reference/decks-validate.js +359 -0
- package/dist/reference/derived-entries.d.ts +5 -1
- package/dist/reference/diagram-fit.d.ts +1 -1
- package/dist/reference/figure-symbols.d.ts +23 -6
- package/dist/reference/figure-symbols.js +51 -15
- package/dist/reference/float-name.d.ts +43 -0
- package/dist/reference/float-name.js +58 -0
- package/dist/reference/folds-validate.d.ts +1 -1
- package/dist/reference/gate-abbreviations.d.ts +1 -1
- package/dist/reference/gate-indices.js +5 -4
- package/dist/reference/gate-math.d.ts +29 -2
- package/dist/reference/gate-math.js +99 -18
- package/dist/reference/gate-part-pages.d.ts +1 -1
- package/dist/reference/lab-katex-validate.d.ts +10 -6
- package/dist/reference/lab-katex-validate.js +17 -9
- package/dist/reference/lab-math-scope.js +29 -4
- package/dist/reference/math-glyphs.d.mts +25 -0
- package/dist/reference/math-glyphs.mjs +189 -25
- package/dist/reference/page-notation.d.ts +22 -3
- package/dist/reference/page-notation.js +32 -10
- package/dist/reference/references.d.ts +14 -1
- package/dist/reference/references.js +13 -0
- package/dist/reference/registry-scope.d.mts +33 -0
- package/dist/reference/registry-scope.mjs +34 -0
- package/dist/reference/registry.js +29 -10
- package/dist/reference/rehype-deck.d.mts +6 -0
- package/dist/reference/rehype-deck.mjs +286 -0
- package/dist/reference/rehype-heading-anchors.d.mts +20 -0
- package/dist/reference/rehype-heading-anchors.mjs +48 -6
- package/dist/reference/remark-notation.mjs +249 -40
- package/dist/reference/source-lines.d.mts +12 -0
- package/dist/reference/source-lines.mjs +65 -0
- package/dist/reference/types.d.ts +28 -0
- package/dist/rules/catalog.d.ts +21 -4
- package/dist/rules/catalog.js +221 -2
- package/dist/rules/course-rules.d.ts +5 -1
- package/dist/rules/course-rules.js +17 -3
- package/dist/rules/course-rules.schema.json +12 -0
- package/dist/rules/in-effect.d.ts +13 -3
- package/dist/rules/in-effect.js +30 -7
- package/dist/rules/rules.schema.json +259 -1
- package/dist/rules/settings.d.ts +1 -1
- package/dist/session/PreferenceStore.d.ts +6 -0
- package/dist/session/PreferenceStore.js +18 -1
- package/dist/session/deck-memory.d.ts +116 -0
- package/dist/session/deck-memory.js +143 -0
- package/dist/session/reading-memory.d.ts +96 -0
- package/dist/session/reading-memory.js +133 -0
- package/docs/VERSIONING.md +21 -18
- package/docs/authoring/README.md +29 -9
- package/docs/authoring/agents.md +63 -16
- package/docs/authoring/components.md +15 -0
- package/docs/authoring/deck.md +588 -0
- package/docs/authoring/migrating.md +1045 -16
- package/docs/authoring/notation.md +49 -6
- package/docs/authoring/structure.md +80 -1
- package/docs/authoring/versioning.md +54 -2
- package/docs/authoring/visualizations.md +7 -2
- package/docs/course-agent/AGENTS.md +37 -0
- package/docs/course-agent/agents/rules-reviewer.md +33 -0
- package/docs/course-agent/skills/draft-lesson/SKILL.md +37 -0
- package/docs/course-agent/skills/migrate-course/SKILL.md +31 -0
- package/docs/course-agent/skills/release-content/SKILL.md +24 -0
- package/docs/diagram-scaling.md +148 -0
- package/docs/privacy.md +59 -0
- package/package.json +8 -2
- package/src/compiler/astro.ts +60 -21
- package/src/compiler/cli-main.ts +469 -0
- package/src/compiler/cli.ts +1905 -16
- package/src/compiler/collections.ts +284 -25
- package/src/compiler/course-config.ts +267 -0
- package/src/compiler/decks.ts +455 -0
- package/src/compiler/exports/deck-fit.ts +127 -0
- package/src/compiler/exports/export-chapters.ts +105 -6
- package/src/compiler/exports/export-pages.ts +46 -2
- package/src/compiler/exports/latex/beamerthemeexplico.sty +124 -0
- package/src/compiler/exports/latex/cache.ts +183 -0
- package/src/compiler/exports/latex/capture.ts +5 -2
- package/src/compiler/exports/latex/compile.ts +18 -0
- package/src/compiler/exports/latex/deck.ts +419 -0
- package/src/compiler/exports/latex/document.ts +117 -21
- package/src/compiler/exports/latex/explico-common.sty +198 -0
- package/src/compiler/exports/latex/explico.cls +13 -183
- package/src/compiler/exports/latex/serialize.ts +25 -21
- package/src/compiler/exports/latex/texlive-packages.txt +6 -2
- package/src/compiler/exports/latex-export.ts +187 -45
- package/src/compiler/exports/markdown/serialize.ts +118 -9
- package/src/compiler/exports/markdown-export.ts +56 -11
- package/src/compiler/exports/standalone-html.ts +8 -7
- package/src/compiler/exports/static-tree.ts +175 -3
- package/src/compiler/knowledge-version.ts +56 -21
- package/src/compiler/lesson-derivation.ts +282 -16
- package/src/compiler/manifest.ts +558 -63
- package/src/compiler/page-versions.ts +359 -0
- package/src/compiler/resolution-report.ts +38 -7
- package/src/compiler/validate-content.ts +212 -0
- package/src/components/CourseContents.astro +124 -6
- package/src/components/CurriculumMap.astro +49 -1
- package/src/components/FloatIndex.astro +1 -1
- package/src/components/PartOverview.astro +51 -5
- package/src/components/assessments/AssessmentSet.astro +41 -15
- package/src/components/block-heading.ts +15 -6
- package/src/components/card-carry.ts +7 -4
- package/src/components/card-manager.ts +14 -2
- package/src/components/card-memory.ts +13 -9
- package/src/components/deck/DeckEnhancer.astro +13 -0
- package/src/components/deck/deck-context.ts +104 -0
- package/src/components/deck/deck-controller.ts +1287 -0
- package/src/components/deck/deck-map.ts +91 -0
- package/src/components/deck-lane.ts +163 -0
- package/src/components/diagram/Diagram.astro +131 -86
- package/src/components/disclosure/persisted-disclosure.ts +9 -1
- package/src/components/figure/Figure.astro +7 -2
- package/src/components/figure/FloatCaption.astro +5 -3
- package/src/components/fold/Fold.astro +2 -2
- package/src/components/notation/NotationFormula.astro +81 -11
- package/src/components/notation/NotationGlossary.astro +132 -1
- package/src/components/notation/NotationLayer.astro +15 -7
- package/src/components/starlight/AboutPanel.astro +186 -3
- package/src/components/starlight/CollapsibleSections.astro +24 -3
- package/src/components/starlight/DiagramEnhancer.astro +5 -1
- package/src/components/starlight/HeadingAnchorEnhancer.astro +1 -1
- package/src/components/starlight/LayoutHeader.astro +17 -0
- package/src/components/starlight/LayoutPageTitle.astro +110 -0
- package/src/components/starlight/LayoutSidebar.astro +807 -21
- package/src/components/starlight/LayoutSocialIcons.astro +62 -6
- package/src/components/starlight/LessonExportActions.astro +27 -9
- package/src/components/starlight/LessonFooter.astro +59 -0
- package/src/components/starlight/ReadingMemoryNotice.astro +114 -0
- package/src/components/starlight/TextSizeControl.astro +24 -13
- package/src/components/starlight/page-marks.ts +139 -0
- package/src/components/starlight/section-summary.ts +1 -1
- package/src/components/starlight/sidebar-editions.ts +245 -0
- package/src/components/table/Table.astro +15 -7
- package/src/content-config.ts +87 -1
- package/src/curriculum/competency-graph.ts +95 -0
- package/src/reference/base-library.mjs +19 -0
- package/src/reference/consistency.ts +126 -39
- package/src/reference/deck-remark.mjs +600 -0
- package/src/reference/deck-source.ts +273 -0
- package/src/reference/decks-validate.ts +604 -0
- package/src/reference/diagram-layout.ts +3 -1
- package/src/reference/figure-symbols.ts +108 -31
- package/src/reference/folds-validate.ts +1 -1
- package/src/reference/gate-indices.ts +5 -3
- package/src/reference/gate-math.ts +169 -25
- package/src/reference/lab-katex-validate.ts +30 -15
- package/src/reference/lab-math-scope.ts +34 -3
- package/src/reference/math-glyphs.mjs +189 -25
- package/src/reference/page-notation.ts +69 -15
- package/src/reference/references.ts +28 -0
- package/src/reference/registry-scope.mjs +34 -0
- package/src/reference/registry.ts +41 -11
- package/src/reference/rehype-deck.mjs +286 -0
- package/src/reference/rehype-heading-anchors.mjs +48 -6
- package/src/reference/remark-notation.mjs +249 -40
- package/src/reference/source-lines.mjs +65 -0
- package/src/reference/types.ts +28 -0
- package/src/rules/catalog.ts +257 -4
- package/src/rules/course-rules.ts +21 -1
- package/src/rules/in-effect.ts +54 -8
- package/src/session/PreferenceStore.ts +23 -0
- package/src/session/deck-memory.ts +263 -0
- package/src/session/reading-memory.ts +243 -0
- package/src/styles/deck.css +1150 -0
- package/src/styles/global.css +958 -27
- package/src/styles/zones.css +277 -0
- /package/src/{components → reference}/float-name.ts +0 -0
|
@@ -128,6 +128,18 @@ _How to apply._ See the `notation` collection schema
|
|
|
128
128
|
(`src/compiler/content-schemas.ts`). A letter that exists only so one entry's own `formula` can
|
|
129
129
|
be stated is a **gloss**, not a card — it creates no curriculum edge.
|
|
130
130
|
|
|
131
|
+
Which tier, in one question: *is this letter already defined?* Ask
|
|
132
|
+
`pnpm content symbol <latex>` first. If a card with this meaning exists,
|
|
133
|
+
reference it (`[[key]]` once in prose, bare math after). If the letter already
|
|
134
|
+
carries another meaning in the course, declare it here: `notation.local` on a
|
|
135
|
+
lesson, a gloss in a card's formula — that reuse is the one reason for the
|
|
136
|
+
page tier, and it needs no acknowledgement. Otherwise make a shared card: the
|
|
137
|
+
next lesson that needs it will reference it instead of copying it. Use count
|
|
138
|
+
plays no part — a constant used on one page is vocabulary, a letter used on
|
|
139
|
+
six pages with six meanings is a reused letter each time — and the sign that
|
|
140
|
+
a meaning was not page-bound is that it got defined twice, which
|
|
141
|
+
`definition-repeated` and `meaning-repeated` report.
|
|
142
|
+
|
|
131
143
|
Do not satisfy this rule by dumping every `[[key]]` a page uses into one
|
|
132
144
|
trailing "Notation used on this page" (or similarly named) section near the
|
|
133
145
|
end of the lesson. `LessonFooter.astro` already injects an interactive
|
|
@@ -266,13 +278,21 @@ $\theta$ after replacing $\theta$ with $-\theta$.
|
|
|
266
278
|
|
|
267
279
|
_Enforcement._ Build-enforced. The completeness gate (`gate-math.ts`, `pnpm
|
|
268
280
|
validate:content`; `astro build` re-runs the same resolver) fails on an
|
|
269
|
-
unresolved glyph (`unresolved-glyph`) in every rendered-math context
|
|
281
|
+
unresolved glyph (`unresolved-glyph`) in every rendered-math context,
|
|
282
|
+
a notation entry's body included: it binds every shared entry, without
|
|
283
|
+
`[[key]]`, and no `notation.local` (`registry-scope.mjs`), and the glossary
|
|
284
|
+
fails `astro build` on an entry whose body did not render;
|
|
270
285
|
`consistency.ts` adds the corpus-level lints `glyph-unique-in-corpus`
|
|
271
|
-
(warning
|
|
272
|
-
|
|
286
|
+
(warning: two shared cards on one glyph — a local entry or a gloss may reuse
|
|
287
|
+
a letter), `definition-repeated` (warning: one local key declared in two
|
|
288
|
+
lessons), `meaning-repeated` (info: one symbol and name defined at two sites,
|
|
289
|
+
in any tier), `weak-local` and `card-wants-demoting` (info); the gate reports
|
|
290
|
+
`explain-redundant` (info) for an `\explain` wrap whose bare symbol already
|
|
291
|
+
resolves to the same card; a final rendered-trigger/payload parity check backs it at render time,
|
|
273
292
|
and a last pass over every built page fails a symbol a figure shows that KaTeX
|
|
274
293
|
drew outside a notation binding (`figure-symbols.ts`: a lab that called KaTeX
|
|
275
|
-
itself — see [V3](visualizations.md#v3))
|
|
294
|
+
itself — see [V3](visualizations.md#v3)); a plain base-library letter is not
|
|
295
|
+
one, here as in a lesson.
|
|
276
296
|
This rule is **locked** — the notation layer is this rule, so a course cannot
|
|
277
297
|
turn it off.
|
|
278
298
|
|
|
@@ -1253,7 +1273,7 @@ text where every other card shows a name.
|
|
|
1253
1273
|
|
|
1254
1274
|
_How to apply._ Keep the name to a few words with no sentence punctuation or
|
|
1255
1275
|
relative clause. If the symbol needs a description, promote the gloss to a
|
|
1256
|
-
notation entry (the `
|
|
1276
|
+
notation entry (the `meaning-repeated` hint under N4 says when).
|
|
1257
1277
|
|
|
1258
1278
|
_Enforcement._ Build-enforced (`consistency.ts` `checkGlossNameShape`, `pnpm
|
|
1259
1279
|
validate:content`) — warning `gloss-name-shape` for a name over the word
|
|
@@ -1415,7 +1435,11 @@ the same precedence [N22](#n22) uses for decorations:
|
|
|
1415
1435
|
object (it needs its own entry, or an `\explain{key}{x}` where it really
|
|
1416
1436
|
is shorthand). A digit fills a running slot, but a `\mathrm{…}` or
|
|
1417
1437
|
`\text{…}` label never does: `x_{\mathrm{e}}` is not an instance of
|
|
1418
|
-
`x_k`.
|
|
1438
|
+
`x_k`. A double index written as juxtaposed letters is one slot of that
|
|
1439
|
+
many letters: the family `x_{ij}` (or `K_{ij}`) gives its card to `x_{i1}`,
|
|
1440
|
+
`x_{12}` and `x_{ji}`, a run of as many letters and digits, but not to
|
|
1441
|
+
`x_i`, `x_{ijk}` or `x_{i+1}`; `x_{i,j}` is the same family with two slots.
|
|
1442
|
+
Two families on one base that both fit an instance fail
|
|
1419
1443
|
`ambiguous-base-fallback`: keep one per page, or `\explain` the instance.
|
|
1420
1444
|
An entry whose index is fixed (`v_0`, `H_1`, `T_{\mathrm{opt}}`, or one
|
|
1421
1445
|
marked `fixedIndex: true`) is a single symbol and lends its card to
|
|
@@ -1457,6 +1481,10 @@ or `d` there needs a card too.
|
|
|
1457
1481
|
|
|
1458
1482
|
A subscript on an operator or a bracket is not an index: `\sum_{i=1}`,
|
|
1459
1483
|
`\lim_{t \to 0}`, `\log_{10}`, `\lVert x\rVert_2`, `\left.f\right\rvert_{x=0}`.
|
|
1484
|
+
Nor is a letter that belongs to a symbol with a card of its own inside the
|
|
1485
|
+
subscript (`b` of `b_n` in `\nabla_{b_n}`; the `b_n` is checked in its own
|
|
1486
|
+
right), or any letter of a subscript that states a condition with a relation
|
|
1487
|
+
(`Z \sim g` in `\mathbb{E}_{Z \sim g}`).
|
|
1460
1488
|
A superscript is a power (`y^{(m)}` is out of scope). An entry whose `latex`
|
|
1461
1489
|
has a superscript and no subscript (`j^{(m)}`) is a whole form for that
|
|
1462
1490
|
symbol and, having no index, still lends its card to its bare base.
|
|
@@ -1491,6 +1519,13 @@ says `fixedIndex: true` (a component `x`, a label). An uppercase or Greek
|
|
|
1491
1519
|
letter in a subscript (`X_N`, `p_\theta`) is a size or a symbol, not a
|
|
1492
1520
|
running index, and needs no set.
|
|
1493
1521
|
|
|
1522
|
+
A letter is fixed everywhere on the page, so `fixedIndex: true` on the card of
|
|
1523
|
+
`m` also turns a family indexed by it (`r_m`) into a single symbol that lends
|
|
1524
|
+
its card to nothing, `r_2` included. A letter that is the index of a family
|
|
1525
|
+
runs over a set: give its card a `range` rather than `fixedIndex`. The
|
|
1526
|
+
`unresolved-glyph` message for an `r_2` that fails this way names the card
|
|
1527
|
+
that fixed the letter.
|
|
1528
|
+
|
|
1494
1529
|
### `\explain` around an indexed symbol
|
|
1495
1530
|
|
|
1496
1531
|
`\explain{key}{v_x}` makes the whole `v_x` one target with `key`'s card. If
|
|
@@ -1501,6 +1536,14 @@ or name an entry for the indexed symbol. An index inside a larger wrap
|
|
|
1501
1536
|
(`\explain{survival-probability}{S(0,t_i)}`) is the wrap's, as every glyph
|
|
1502
1537
|
inside one is.
|
|
1503
1538
|
|
|
1539
|
+
The wrap claims the indexed symbol, so its base takes no other entry's card:
|
|
1540
|
+
`\explain{component-sd}{\sigma_1}` under the family `\sigma_k` leaves the `\sigma`
|
|
1541
|
+
alone even when the page also has a plain `\sigma`. The other direction nests:
|
|
1542
|
+
an `\explain` inside an instance's own subscript,
|
|
1543
|
+
`\beta_{\explain{regressor-count}{K}}`, still finds the family `\beta_j`, and
|
|
1544
|
+
the instance's wrap goes around it, as
|
|
1545
|
+
`\explain{beta-j}{\beta_{\explain{regressor-count}{K}}}`.
|
|
1546
|
+
|
|
1504
1547
|
| Code | Severity | When |
|
|
1505
1548
|
| --- | --- | --- |
|
|
1506
1549
|
| `index-explained` | error | a subscript on a symbol has no whole form, family or index entry, and a slot has no letter with its own card — a digit, a label, a base-library letter — or the base itself has no card |
|
|
@@ -65,6 +65,19 @@ Consequences:
|
|
|
65
65
|
is that same unit's position in the reading order. A file is a lesson; its
|
|
66
66
|
ordinal is its chapter number.
|
|
67
67
|
|
|
68
|
+
### A lesson's two editions
|
|
69
|
+
|
|
70
|
+
A lesson can have two editions: its **chapter**, `<name>.mdx`, and its
|
|
71
|
+
**deck**, `<name>.deck.mdx`, the slide edition ([deck.md](deck.md)). A deck
|
|
72
|
+
beside its chapter is served at `<lesson>/slides/`, and the lesson's one
|
|
73
|
+
sidebar row opens one edition and carries the other as an icon
|
|
74
|
+
([deck.md](deck.md#in-the-sidebar)). It is not a level of its own and takes
|
|
75
|
+
no chapter number. A
|
|
76
|
+
lesson with a deck and no chapter is **slides-first**: the deck is the lesson,
|
|
77
|
+
at the lesson's own URL, with a chapter number in its part, so the chapters
|
|
78
|
+
after it keep theirs. Inside a deck, `##` opens a section of frames and `###` is
|
|
79
|
+
a frame (rule D1), so levels 4 and 5 read as section and frame there.
|
|
80
|
+
|
|
68
81
|
## What the playground demonstrates
|
|
69
82
|
|
|
70
83
|
The playground is the worked example for every level. It was deliberately
|
|
@@ -174,7 +187,9 @@ reading order with their descriptions, the competencies the part teaches, the
|
|
|
174
187
|
parts it builds on and leads to — so the page cannot fall out of date with the
|
|
175
188
|
content. Only the two paragraphs and the optional `chain` (the part's argument,
|
|
176
189
|
one step per line, which no manifest field knows) are authored. `rail={false}` drops the
|
|
177
|
-
side rail and keeps the reading list.
|
|
190
|
+
side rail and keeps the reading list. In a course with a deck its reading list
|
|
191
|
+
has the contents list's glyphs and slides column (below); `slides={false}`
|
|
192
|
+
drops them.
|
|
178
193
|
|
|
179
194
|
### Study time
|
|
180
195
|
|
|
@@ -232,6 +247,70 @@ cannot leave a stale copy behind on the front page. `descriptions={false}`
|
|
|
232
247
|
drops the one-line summaries; `headingLevel={3}` fits it under an existing
|
|
233
248
|
`##`.
|
|
234
249
|
|
|
250
|
+
**Slides.** When the course has a deck, the list says which lessons have
|
|
251
|
+
slides, whatever the reader's sidebar controls show: it answers "what is in
|
|
252
|
+
the course", and the controls only choose what a sidebar row opens.
|
|
253
|
+
|
|
254
|
+
- Each title starts with the glyph of what it opens: a chapter's lined sheet,
|
|
255
|
+
or a slides-only lesson's slide frame. A part's heading starts with
|
|
256
|
+
Overview's two sheets.
|
|
257
|
+
- The counts gain the decks ("4 slide decks"), and a key under them names the
|
|
258
|
+
glyphs the list uses: Chapter, Slides, and Slides for a whole part when a
|
|
259
|
+
part has a deck.
|
|
260
|
+
- A slides column ends every row. A lesson's own deck is a tile on its title
|
|
261
|
+
line. A part's deck is one bar along the chapters it covers, from the first
|
|
262
|
+
covered title to the last, its first cell carrying the part deck's two
|
|
263
|
+
frames; each cell links to that chapter's section of the deck and is named
|
|
264
|
+
as the sidebar names it ("Slides: *chapter*", "Slides 17–25 of the part's
|
|
265
|
+
slides"). A part's deck that skips a chapter breaks into runs; one that
|
|
266
|
+
covers a chapter with a deck of its own runs in a second column. A
|
|
267
|
+
slides-only lesson's tile is out of the tab order, since its title opens the
|
|
268
|
+
same page.
|
|
269
|
+
|
|
270
|
+
A course with no deck renders the list as before. `slides={false}` drops the
|
|
271
|
+
column, the glyphs, the key and the count; `<PartOverview slides={false} />`
|
|
272
|
+
does the same on a part's page.
|
|
273
|
+
|
|
274
|
+
## When the path is not in the course
|
|
275
|
+
|
|
276
|
+
Author `content/docs/404.mdx` as a splash page. Starlight serves that entry for
|
|
277
|
+
an unknown URL; without it, the fallback page works but every build warns that
|
|
278
|
+
the `docs` entry `404` was not found. A minimal page is:
|
|
279
|
+
|
|
280
|
+
```mdx
|
|
281
|
+
---
|
|
282
|
+
title: "404: Page outside the syllabus"
|
|
283
|
+
description: The requested course page could not be found.
|
|
284
|
+
template: splash
|
|
285
|
+
hero:
|
|
286
|
+
title: "404"
|
|
287
|
+
tagline: We followed this URL as far as the course map goes. It does not land on a page.
|
|
288
|
+
pagefind: false
|
|
289
|
+
sidebar:
|
|
290
|
+
hidden: true
|
|
291
|
+
editUrl: false
|
|
292
|
+
---
|
|
293
|
+
|
|
294
|
+
import { LinkButton } from '@astrojs/starlight/components';
|
|
295
|
+
|
|
296
|
+
<div style="display: flex; justify-content: center;">
|
|
297
|
+
<LinkButton href={import.meta.env.BASE_URL} icon="left-arrow" iconPlacement="start">
|
|
298
|
+
Return to the course
|
|
299
|
+
</LinkButton>
|
|
300
|
+
</div>
|
|
301
|
+
```
|
|
302
|
+
|
|
303
|
+
Use `import.meta.env.BASE_URL` for a link to the course root: a relative `./`
|
|
304
|
+
would resolve against the missing URL, not against `404.html`, while a bare `/`
|
|
305
|
+
would escape a subpath deployment. In `astro.config.mjs`, set
|
|
306
|
+
`disable404Route: process.env.NODE_ENV === 'production'` in `starlight({ … })`:
|
|
307
|
+
Starlight catches arbitrary missing paths in development, while the docs entry
|
|
308
|
+
owns `/404.html` in a static build without colliding with the injected route.
|
|
309
|
+
The 404 is a utility page, not a chapter: it has no `teaches`, notation,
|
|
310
|
+
knowledge check, numbering, sidebar row
|
|
311
|
+
or lesson exports. Set Astro's top-level `site` to the published origin as well
|
|
312
|
+
so Starlight can write its sitemap; `base` continues to hold any subpath.
|
|
313
|
+
|
|
235
314
|
## Where the reader is
|
|
236
315
|
|
|
237
316
|
Two marks, both derived, neither authored:
|
|
@@ -4,7 +4,9 @@
|
|
|
4
4
|
learning-facing content, independent of the `explico` engine's own version. The
|
|
5
5
|
declared value is `about.version` in `content/course.config.ts`; the history and
|
|
6
6
|
the reader-facing notes are in `content/CHANGELOG.md`. Both are surfaced to
|
|
7
|
-
readers by the About `[?]` panel.
|
|
7
|
+
readers by the About `[?]` panel. Each chapter and part page also carries the
|
|
8
|
+
version its own content last changed in ([C5](#c5)), kept in
|
|
9
|
+
`content/versions.json`, which the build writes.
|
|
8
10
|
|
|
9
11
|
See [README.md](README.md) for what "Build-enforced" and "Review-only" mean here.
|
|
10
12
|
The engine's own versioning is a separate policy —
|
|
@@ -91,7 +93,9 @@ one commit:
|
|
|
91
93
|
2. Rename the heading to `## [x.y.z] - <today>`; add a fresh empty
|
|
92
94
|
`## [Unreleased]` above it.
|
|
93
95
|
3. Set `about.version` in `content/course.config.ts` to `x.y.z`.
|
|
94
|
-
4. `pnpm validate:content` — no `knowledge-*` warning.
|
|
96
|
+
4. `pnpm validate:content` — no `knowledge-*` warning. It rewrites
|
|
97
|
+
`content/versions.json`, where every page changed since the last release
|
|
98
|
+
takes `x.y.z` ([C5](#c5)); commit it in the same commit.
|
|
95
99
|
|
|
96
100
|
`pnpm release:content` does steps 2 and 3 for you.
|
|
97
101
|
|
|
@@ -106,3 +110,51 @@ edits landed in one commit — it warns until the second half lands.
|
|
|
106
110
|
|
|
107
111
|
_Exceptions._ None.
|
|
108
112
|
|
|
113
|
+
---
|
|
114
|
+
|
|
115
|
+
## C5
|
|
116
|
+
|
|
117
|
+
**Each chapter and part page carries the knowledge version its content last
|
|
118
|
+
changed in, kept in `content/versions.json`, which `validate:content` writes
|
|
119
|
+
and the course commits with the change.**
|
|
120
|
+
|
|
121
|
+
Under a chapter's title, its PDF and Markdown say "unchanged since version
|
|
122
|
+
0.2.0", the release its content last changed in, not the course's current
|
|
123
|
+
version; a chapter changed since the last release says "with changes after
|
|
124
|
+
version 0.4.0, not yet released". The `[?]` panel shows the same line for the
|
|
125
|
+
page it is on, and a part's or the course's PDF carries the newest version
|
|
126
|
+
among its pages. A document whose version did not move keeps its PDF's date
|
|
127
|
+
and identifier, so a release recompiles only what it changed
|
|
128
|
+
(`EXPLICO_LATEX_CACHE`).
|
|
129
|
+
|
|
130
|
+
_Rationale._ The course's version moves at every release, whatever the release
|
|
131
|
+
touched, so stamped on a chapter it only says "built after 0.4.0". A reader
|
|
132
|
+
wants to know whether the chapter they printed is the one on the site. A build
|
|
133
|
+
sees one commit and cannot tell what a page was at an earlier release, so the
|
|
134
|
+
record is a committed file, kept like a lockfile: the build derives it, the
|
|
135
|
+
course commits it.
|
|
136
|
+
|
|
137
|
+
_How to apply._ Nothing by hand. `pnpm validate:content` writes
|
|
138
|
+
`content/versions.json` from the committed one and the content; commit what it
|
|
139
|
+
wrote with every content change, and with every release ([C3](#c3) step 4).
|
|
140
|
+
What counts as the page: its own file, its `.checks.yml` and the assessments it
|
|
141
|
+
lists, the shared notation entries its text uses, the sources it cites, and its
|
|
142
|
+
numbers (`part.chapter` and the float offsets its earlier chapters leave it). A
|
|
143
|
+
page changed while `## [Unreleased]` holds any bullet is "not yet released"
|
|
144
|
+
until the next release; a change with no bullet (a fix [C1](#c1) lets go
|
|
145
|
+
unlisted) keeps the current version. A lab, `content/domain/`, a part's label
|
|
146
|
+
or the engine change a page's PDF without changing its version. Rule off
|
|
147
|
+
(`rules.C5.enabled: false` in the course settings): the file is neither read
|
|
148
|
+
nor written, and every page carries the course's version.
|
|
149
|
+
|
|
150
|
+
_Enforcement._ Build-enforced (`page-versions.ts`). `pnpm validate:content` on
|
|
151
|
+
a developer's machine rewrites a stale `content/versions.json` and says so; in
|
|
152
|
+
CI (`CI` set) it fails with `page-versions-stale` (`syncPageVersions`), so a
|
|
153
|
+
content change cannot land without its record. A file that is not a record
|
|
154
|
+
`validate:content` wrote warns `page-versions-invalid` (`readPageVersions`) and
|
|
155
|
+
is rebuilt, every page counting as new. Partial: which bullet of
|
|
156
|
+
`## [Unreleased]` belongs to which page is not known, so every page changed
|
|
157
|
+
while there is any bullet counts as not yet released.
|
|
158
|
+
|
|
159
|
+
_Exceptions._ None. A course that does not want the stamp turns the rule off.
|
|
160
|
+
|
|
@@ -207,9 +207,14 @@ component.
|
|
|
207
207
|
_Enforcement._ Partly build-enforced. `pnpm validate:content` follows each
|
|
208
208
|
lesson's relative imports to the course's components and fails one that
|
|
209
209
|
imports `katex` itself (`lab-katex-validate.ts`, `lab-katex-direct`, an
|
|
210
|
-
error), naming the chain of imports that reaches it
|
|
210
|
+
error), naming the chain of imports that reaches it, once for every lesson that
|
|
211
|
+
reaches the file; a lesson that reaches none is clean. `astro build` then reads
|
|
211
212
|
every figure it rendered and fails a symbol KaTeX drew outside a notation
|
|
212
|
-
binding (`figure-symbols.ts`, rule [N4](notation.md#n4))
|
|
213
|
+
binding (`figure-symbols.ts`, rule [N4](notation.md#n4)), listing every page
|
|
214
|
+
that has one rather than the first. A plain base-library `d`, `e`, `i` or `π`
|
|
215
|
+
(`renderLabMath` leaves it without a card, as a lesson's math does) is not such
|
|
216
|
+
a symbol, in a power too; a decorated or styled one is, and a library letter as
|
|
217
|
+
an index is rule [N23](notation.md#n23)'s, checked as the lab renders. A course's browser
|
|
213
218
|
test runs the same `unboundFigureSymbols` on a hydrated figure's
|
|
214
219
|
`outerHTML`, which also holds what an island draws only in the browser, as
|
|
215
220
|
the playground's `labs.spec.ts` does. The same build warns about a figure
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# AGENTS.md
|
|
2
|
+
|
|
3
|
+
This course is built on [explico](https://www.npmjs.com/package/explico). Read
|
|
4
|
+
`node_modules/explico/docs/authoring/agents.md` first: it is the engine's guide
|
|
5
|
+
for an agent working on a course, and it ships with the version installed here.
|
|
6
|
+
This file holds only what is particular to this course. Two complete courses to
|
|
7
|
+
compare against — the playground, which the engine's docs cite as
|
|
8
|
+
`playground/…`, and credit-and-derivatives — are downloadable from
|
|
9
|
+
https://danieltuzes.github.io/explico/start/ ([`sources.json`](https://danieltuzes.github.io/explico/downloads/sources.json)).
|
|
10
|
+
|
|
11
|
+
## The loop
|
|
12
|
+
|
|
13
|
+
- `pnpm content help` lists the CLI. Start every task there, not with a file
|
|
14
|
+
hunt: `content context <lesson>` for a lesson's scope, `content rules
|
|
15
|
+
[--element …]` for the rules this course has on, `content rule <ID>` for one
|
|
16
|
+
of them whole, `content symbol <latex>` before declaring any notation.
|
|
17
|
+
- While writing math: `content equation "<latex>" --lesson <id>`; after:
|
|
18
|
+
`content bindings <lesson>` and `content check <lesson>`.
|
|
19
|
+
- Before handing back: `pnpm validate:content` (passes on its first run; commit
|
|
20
|
+
`content/versions.json` if it changed), `pnpm content check --changed`,
|
|
21
|
+
`astro check`, `astro build`.
|
|
22
|
+
- Every content change adds a bullet under `## [Unreleased]` in
|
|
23
|
+
`content/CHANGELOG.md` (rule C1).
|
|
24
|
+
- Upgrading `explico`: `pnpm content migrate` prints exactly the sections to
|
|
25
|
+
apply.
|
|
26
|
+
|
|
27
|
+
The procedures above are project skills under `.claude/skills/` and the
|
|
28
|
+
`rules-reviewer` agent under `.claude/agents/`, installed by
|
|
29
|
+
`pnpm content init-agent --write` from the engine's `docs/course-agent/`;
|
|
30
|
+
rerun it after an upgrade to refresh them.
|
|
31
|
+
|
|
32
|
+
## This course
|
|
33
|
+
|
|
34
|
+
<!-- What an agent needs that the engine's guide cannot know: the course's
|
|
35
|
+
subject and audience, where its sources live, its own `X` rules and why,
|
|
36
|
+
parts that are off limits, how it deploys. Keep it short; it is read on
|
|
37
|
+
every turn. -->
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: rules-reviewer
|
|
3
|
+
description: Reviews one explico lesson against the guide-only authoring rules (the ones no script checks) in a fresh context, and returns findings with line numbers in at most a thousand tokens. Use before handing a drafted or revised lesson back; the draft-lesson skill invokes it. Give it the lesson id and, when the course is not the playground, the course package path.
|
|
4
|
+
tools: Bash, Read, Grep, Glob
|
|
5
|
+
model: inherit
|
|
6
|
+
effort: medium
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
You review one lesson of an explico course against the rules a script cannot
|
|
10
|
+
check. You are the second reader the drafter does not have: fresh context,
|
|
11
|
+
only the rules and the lesson in it. Return findings, not a rewrite.
|
|
12
|
+
|
|
13
|
+
Inputs: a lesson id such as `projectiles.projectile-range`, and the course
|
|
14
|
+
package directory (the one whose `package.json` has the `content` script;
|
|
15
|
+
`playground/` in the explico repository). Run the CLI from that directory
|
|
16
|
+
with `pnpm -s content …`.
|
|
17
|
+
|
|
18
|
+
1. `pnpm -s content check <lesson>`: if it fails, report the failure first and
|
|
19
|
+
stop — a build error outranks style.
|
|
20
|
+
2. `pnpm -s content context <lesson> --json > /tmp/ctx.json` is too big to
|
|
21
|
+
read whole; instead `pnpm -s content context <lesson>` for the header, then
|
|
22
|
+
read the lesson file (`content/docs/<slug>.mdx`) with line numbers.
|
|
23
|
+
3. `pnpm -s content rules --enforcement guide` lists the rules this course has
|
|
24
|
+
on that no script checks, by element, each with a one-line summary. Apply
|
|
25
|
+
only those; a rule absent from the list is off. Open `pnpm -s content rule
|
|
26
|
+
<ID>` only when a summary does not settle a doubt.
|
|
27
|
+
4. Read the lesson once, rule list beside it, and note each place a guide-only
|
|
28
|
+
rule is not followed: the rule id, the line, what you saw, the fix in one
|
|
29
|
+
clause. A JSX prop string (`caption="…"`) is not prose for the math rules.
|
|
30
|
+
5. Return at most twelve findings, highest impact first, as lines of the form
|
|
31
|
+
`L<line> <ID>: <what> — <fix>`, then one line `ok: <rules checked>` naming
|
|
32
|
+
the rule ids you applied and found satisfied. No preamble, no praise, no
|
|
33
|
+
restatement of the rules. Under a thousand tokens.
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: draft-lesson
|
|
3
|
+
description: Draft or revise an explico lesson with the content CLI doing the lookups — context, rules in effect, symbol lookup before declaring notation, equation and bindings probes before a build, check, the content changelog. Use when asked to write, extend or fix a lesson, a notation entry or an assessment.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Draft a lesson
|
|
7
|
+
|
|
8
|
+
Read with the CLI, not by opening files; every verb prints a bounded digest
|
|
9
|
+
and `--json` for the whole. Run them from the course package (the directory
|
|
10
|
+
whose `package.json` has the `content` script); `pnpm content help` lists them.
|
|
11
|
+
|
|
12
|
+
1. **Scope.** `pnpm content context <lesson>`: the lesson plus exactly its
|
|
13
|
+
notation, references, assumptions and sources (a new lesson:
|
|
14
|
+
`pnpm content new lesson <id>` scaffolds it).
|
|
15
|
+
2. **Rules.** `pnpm content rules --element <prose,table,…>` lists only the
|
|
16
|
+
rules this course has on, with enforcement and codes; `pnpm content rule
|
|
17
|
+
<ID>` prints one whole, example included. A rule absent from the list is
|
|
18
|
+
off: neither apply it nor flag it.
|
|
19
|
+
3. **Notation, one question per symbol: is it already defined?**
|
|
20
|
+
`pnpm content symbol <latex>`. A card with this meaning exists → reference
|
|
21
|
+
it: `[[key]]` once in prose, bare math after; `\explain{key}{…}` only for a
|
|
22
|
+
compound or a colliding glyph. The letter already means something else in
|
|
23
|
+
the course → `notation.local` on this lesson (a gloss, in a card's
|
|
24
|
+
formula). Neither → a shared card (`pnpm content new term <key>`), so the
|
|
25
|
+
next lesson references it instead of copying it. Use count plays no part.
|
|
26
|
+
4. **Math, while writing.** `pnpm content equation "<latex>" --lesson <id>`
|
|
27
|
+
resolves one expression the way the gate will and exits 1 when the build
|
|
28
|
+
would refuse it; `pnpm content bindings <lesson>` shows every span's
|
|
29
|
+
bindings, unresolved glyphs and redundant wraps.
|
|
30
|
+
5. **Check.** `pnpm content check <lesson>` (schema, references, MDX → KaTeX;
|
|
31
|
+
exits non-zero on an error), then `pnpm content diagnostics --lesson <id>`
|
|
32
|
+
for the rest; the counts come first, the lines on request.
|
|
33
|
+
6. **Record.** A bullet under `## [Unreleased]` in `content/CHANGELOG.md`
|
|
34
|
+
(rule C1); `pnpm validate:content` rewrites `content/versions.json`, which
|
|
35
|
+
is committed with the change (rule C5). Guide-only rules (`enforcement
|
|
36
|
+
guide`) have no check: before handing back, have the `rules-reviewer`
|
|
37
|
+
agent review the lesson, or review the draft against them yourself.
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: migrate-course
|
|
3
|
+
description: Upgrade a downstream explico course to a newer engine version — record the old version, install the new one, apply exactly the migration sections between them with `content migrate`, verify. Use when asked to update, upgrade or migrate a course's explico dependency, including a PATCH.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Upgrade a course's engine
|
|
7
|
+
|
|
8
|
+
Every `explico` bump is a migration task, PATCH included: each release has a
|
|
9
|
+
section in the shipped `docs/authoring/migrating.md` that either gives exact
|
|
10
|
+
work or says no course changes are required.
|
|
11
|
+
|
|
12
|
+
1. Record the installed version: `node -p "require('explico/package.json').version"`.
|
|
13
|
+
2. `pnpm add explico@<version>` — without `-D`, so pnpm updates whichever block
|
|
14
|
+
already lists `explico`.
|
|
15
|
+
3. `pnpm content migrate` prints exactly the sections to apply, from the
|
|
16
|
+
version `package.json` pinned at HEAD to the one now installed, oldest
|
|
17
|
+
first (`--from`/`--to` override either end), and refuses when a section is
|
|
18
|
+
missing. Apply every one: required and conditional changes across
|
|
19
|
+
`content/`, `content/course.config.ts`, Astro/source wiring, course scripts,
|
|
20
|
+
dependencies and generated files. Do not use `CHANGELOG.md` alone as the
|
|
21
|
+
procedure, and do not assume a release without a **Breaking** heading
|
|
22
|
+
needs no work. If `pnpm content migrate` is not a verb yet (a course
|
|
23
|
+
whose `scripts/content.ts` predates the shim), read the sections from
|
|
24
|
+
`node_modules/explico/docs/authoring/migrating.md`; the section that
|
|
25
|
+
introduced the shim says how to switch to it.
|
|
26
|
+
4. The checks each section ends with, then: `pnpm validate:content` (passes on
|
|
27
|
+
its first run; commit `content/versions.json` if it changed),
|
|
28
|
+
`pnpm content check --changed`, `astro check`, `astro build`, and the
|
|
29
|
+
course's browser tests if it has any.
|
|
30
|
+
5. Commit the dependency change, the migration edits and the generated files
|
|
31
|
+
together, naming the versions in the message.
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: release-content
|
|
3
|
+
description: Cut a course content release (rules C1–C3, C5) — move content/CHANGELOG.md's Unreleased section to a dated heading and bump about.version together. Use when asked to release or publish a course's content version.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Cut a content release
|
|
7
|
+
|
|
8
|
+
A course's content has its own version stream: `content/course.config.ts`
|
|
9
|
+
`about.version` (shown to readers in the About `[?]` panel) and
|
|
10
|
+
`content/CHANGELOG.md`, with rules C1–C3 in `docs/authoring/versioning.md` and
|
|
11
|
+
each page's own version in `content/versions.json` (rule C5).
|
|
12
|
+
|
|
13
|
+
1. Every change since the last release has a bullet under `## [Unreleased]`
|
|
14
|
+
in `content/CHANGELOG.md` (rule C1). If one is missing, add it first.
|
|
15
|
+
2. Pick the bump (rule C2): MAJOR when a lesson, competency or notation key
|
|
16
|
+
was removed or renamed or a stated outcome changed; MINOR for something
|
|
17
|
+
new; PATCH for a fix or clarification.
|
|
18
|
+
3. From the course package: `pnpm release:content [major|minor|patch]`. It
|
|
19
|
+
moves `[Unreleased]` to a dated `## [x.y.z]` heading and sets
|
|
20
|
+
`about.version` to match in the same change (rule C3).
|
|
21
|
+
4. `pnpm validate:content`: no `knowledge-*` warning; it rewrites
|
|
22
|
+
`content/versions.json` — commit that file with the release, never edit it
|
|
23
|
+
by hand.
|
|
24
|
+
5. Commit the changelog, the config and `versions.json` together.
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
# Diagram scaling on touch screens
|
|
2
|
+
|
|
3
|
+
> **Status.** In `[Unreleased]` (see [`CHANGELOG.md`](../CHANGELOG.md)).
|
|
4
|
+
> Checked in Chromium, in the end-to-end suite and by measurement. **Not
|
|
5
|
+
> checked in WebKit** (Safari, every iOS browser): no WebKit build is available
|
|
6
|
+
> to the agent sessions that wrote it. It uses the technique the print path
|
|
7
|
+
> already uses for WebKit, but a person should look at a diagram on an iPhone
|
|
8
|
+
> before a release ([Checking it](#checking-it)).
|
|
9
|
+
|
|
10
|
+
A diagram is drawn in px at build time. On a phone, the reader's own text-size
|
|
11
|
+
menu shrinks the whole drawing, boxes and text together, so that a wide diagram
|
|
12
|
+
fits the screen instead of scrolling. This page says how, why it is built the
|
|
13
|
+
way it is, and what an agent must not undo.
|
|
14
|
+
|
|
15
|
+
## What a reader sees
|
|
16
|
+
|
|
17
|
+
- On a touch screen (`@media (hover: none)`) the phone menu has a text-size
|
|
18
|
+
select beside the theme switch: **50, 59, 71, 84, 100%**, smallest first. The
|
|
19
|
+
steps are a geometric series, each 2^(1/4) (about 1.19) times the one below.
|
|
20
|
+
A screen with hover has the browser's zoom instead and shows no control.
|
|
21
|
+
- The choice scales the root font size, so every `rem` follows: prose, math,
|
|
22
|
+
spacing, cards.
|
|
23
|
+
- A diagram in the lesson shrinks by the same factor, **boxes and text
|
|
24
|
+
together**. The playground's `#dia-range-derivation` is 687 px wide; in a
|
|
25
|
+
390 px viewport it becomes 577, 488, 405 and 344 px at 84, 71, 59 and 50%.
|
|
26
|
+
The column is 358 to 374 px wide there, so at 50% it fits with no
|
|
27
|
+
scrolling, and the widen button ↔ goes away. At 59% it still scrolls a
|
|
28
|
+
little, and the button stays.
|
|
29
|
+
- A diagram in a preview card (`[[dia-…]]`) keeps its size: a card never takes
|
|
30
|
+
its text under 12 px.
|
|
31
|
+
- With no size chosen (100%), on a screen with hover, and in print, **nothing
|
|
32
|
+
changes**.
|
|
33
|
+
|
|
34
|
+
## How it works
|
|
35
|
+
|
|
36
|
+
| Piece | Where | Role |
|
|
37
|
+
| --- | --- | --- |
|
|
38
|
+
| The steps | `src/components/starlight/TextSizeControl.astro` | Builds the list from `round(100 · 2^((i−4)/4))`, `i = 0…4`. Saves the choice in `localStorage` (`explico:display:text-size`); an inline script copies it to `data-text-size` on `<html>` before first paint. 100% is no attribute. A saved value that is not a step (an earlier release's 90, 80, 70) is ignored. |
|
|
39
|
+
| The scale | `src/styles/global.css`, `@media screen and (hover: none)` | One `:root[data-text-size='…']` rule per step below 100 sets `--explico-text-scale` (0.5 to 0.84). `:root[data-text-size]` sets the root `font-size` to `calc(var(--explico-text-scale, 1) * 100%)`. Outside that media query the variable is unset, so `var(…, 1)` is 1. |
|
|
40
|
+
| The markup | `src/components/diagram/Diagram.astro` | `.diagram-scroll > .diagram-frame > .diagram-canvas > svg`. The frame carries `--diagram-width` and `--diagram-height` (the build-time layout size, unitless px; the canvas inherits them). The canvas keeps its inline `width`/`height` in px. |
|
|
41
|
+
| The rules | `Diagram.astro` `<style>` | Under the same media query, inside `:root[data-text-size] .sl-markdown-content`: the **frame** gets `width/height = layout size × scale` and `overflow: hidden`; the **canvas** gets `transform: scale(var(--explico-text-scale))` with `transform-origin: 0 0`. |
|
|
42
|
+
| The widen button | `src/components/starlight/DiagramEnhancer.astro` | Decides "does it overflow" from the scroller's `scrollWidth`. Its `ResizeObserver` watches the scroller **and the frame**, because a size change resizes the frame, not the scroller. |
|
|
43
|
+
|
|
44
|
+
The node text stays `font-size: 13.6px` (`.diagram-node-content`) at every
|
|
45
|
+
size. The scale comes from the transform, not from the font.
|
|
46
|
+
|
|
47
|
+
## Why it is built this way
|
|
48
|
+
|
|
49
|
+
- **Boxes are measured for 13.6 px text.** `src/reference/diagram-layout.ts`
|
|
50
|
+
(`NODE_FONT_PX`, the insets, the KaTeX formula measure) sizes each box from
|
|
51
|
+
the label and formula at build time. A box can only hold its text if both
|
|
52
|
+
keep their relationship, so the finished drawing is scaled as one thing.
|
|
53
|
+
- **Shrinking only the font is not enough.** That was the first version here:
|
|
54
|
+
the text followed the menu and the boxes did not, so the diagram never got
|
|
55
|
+
narrower and did not fit the phone, which is the point of the menu.
|
|
56
|
+
- **Never scale through the SVG.** Not the `viewBox`, not `width`/`height`
|
|
57
|
+
with a `viewBox`, not a `transform` on a `<g>` or on the `<svg>`. Nodes are
|
|
58
|
+
HTML in a `<foreignObject>`, and WebKit paints that HTML (every KaTeX
|
|
59
|
+
`position: relative` span has its own render layer) **without** the SVG's
|
|
60
|
+
internal transforms: formulas pile up at the SVG origin while their boxes
|
|
61
|
+
stay put. `tests/e2e/site.spec.ts` guards the markup for this. See also the
|
|
62
|
+
comments in `Diagram.astro` and in `global.css` (print).
|
|
63
|
+
- **Scale the HTML canvas around the SVG instead.** The SVG stays at its
|
|
64
|
+
build-time size and the `div` around it takes a CSS `transform`. The print
|
|
65
|
+
path in `global.css` already does exactly this for WebKit, so this is not a
|
|
66
|
+
new technique, only a new trigger. CSS `zoom` was not tried: it is expected to
|
|
67
|
+
go through the SVG root's own scaling in WebKit, the thing being avoided, but
|
|
68
|
+
that is a reading of the engine, not a test.
|
|
69
|
+
- **A frame takes the scaled size.** A transform moves no layout box, so the
|
|
70
|
+
canvas would still be its unscaled size in the flow (extra height, and a
|
|
71
|
+
scroll width of the unscaled width in an engine that counts the layout box).
|
|
72
|
+
The frame has the scaled `width` and `height`, which is what the scroller
|
|
73
|
+
measures, and `overflow: hidden` so the canvas's unscaled layout box adds
|
|
74
|
+
nothing. The scaled canvas exactly fills the frame, so nothing visible is
|
|
75
|
+
clipped.
|
|
76
|
+
- **Only the lesson's own diagrams scale.** The rules are scoped to
|
|
77
|
+
`.sl-markdown-content`. That covers the article and an expanded diagram (the
|
|
78
|
+
enhancer moves the figure to `<body>` inside a `.sl-markdown-content`
|
|
79
|
+
wrapper, to keep every rule scoped that way) and leaves preview cards out.
|
|
80
|
+
|
|
81
|
+
## Rules for changing it
|
|
82
|
+
|
|
83
|
+
1. Do not set a `font-size` in `.diagram-node-content` that depends on
|
|
84
|
+
`--explico-text-scale`. That scales the text twice.
|
|
85
|
+
2. Do not scale through the SVG (see above). A change that adds a `transform`
|
|
86
|
+
or a different `viewBox` to anything inside the `<svg>` breaks WebKit.
|
|
87
|
+
3. Keep the frame's size and the canvas's transform driven by the same
|
|
88
|
+
variable, or the box and its content disagree.
|
|
89
|
+
4. Keep `--diagram-width` and `--diagram-height` as unitless numbers on the
|
|
90
|
+
frame: the rules multiply them by `1px` and by the scale.
|
|
91
|
+
5. A new consumer of a diagram's size (a script, a test) reads the **frame**
|
|
92
|
+
or the scroller's `scrollWidth`, not the canvas's `offsetWidth`, which is
|
|
93
|
+
the unscaled size.
|
|
94
|
+
6. Course CSS or tests that selected `.diagram-scroll > .diagram-canvas` as a
|
|
95
|
+
child need a descendant selector now.
|
|
96
|
+
7. Print and the exports must stay as they were: the scaling rules are inside
|
|
97
|
+
`@media screen and (hover: none)`, and the print rules in `global.css` set
|
|
98
|
+
the canvas's own size and transform. A change here must not leak out of
|
|
99
|
+
that media query.
|
|
100
|
+
|
|
101
|
+
### Changing the steps
|
|
102
|
+
|
|
103
|
+
The list is generated in `TextSizeControl.astro` (`STEPS` and the formula);
|
|
104
|
+
the select and the saved-value check both read it. The CSS cannot be
|
|
105
|
+
generated, so add or remove the matching `:root[data-text-size='…']` rule in
|
|
106
|
+
`global.css`, and the `STEPS` constant in
|
|
107
|
+
`playground/tests/e2e/text-size.spec.ts`. That spec fails if the select's
|
|
108
|
+
values and the formula disagree, and its diagram test fails on a step whose
|
|
109
|
+
CSS rule is missing.
|
|
110
|
+
|
|
111
|
+
## Checking it
|
|
112
|
+
|
|
113
|
+
```sh
|
|
114
|
+
# unit tests, then the specs for this feature (Chromium)
|
|
115
|
+
pnpm test
|
|
116
|
+
pnpm --filter explico-playground exec playwright test \
|
|
117
|
+
tests/e2e/text-size.spec.ts tests/e2e/about.spec.ts tests/e2e/site.spec.ts \
|
|
118
|
+
tests/e2e/diagram-fit.spec.ts tests/e2e/print-export.spec.ts
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
`text-size.spec.ts` checks, at each step on a 390 px phone: the root size, the
|
|
122
|
+
canvas transform (`matrix(s, 0, 0, s, 0, 0)`), the frame's width (`built × s`),
|
|
123
|
+
the scroller's `scrollWidth`, that the node text is still 13.6 px and clipped
|
|
124
|
+
nowhere, that at 50% the 687 px diagram fits and its widen button is hidden, and
|
|
125
|
+
that a screen with hover ignores a saved size.
|
|
126
|
+
|
|
127
|
+
**What Chromium cannot show is WebKit's paint.** Before a release, on an iPhone
|
|
128
|
+
(or Safari's responsive mode, which uses the desktop engine and so is not
|
|
129
|
+
enough on its own), open a lesson with a diagram that has formulas, pick each
|
|
130
|
+
size in the phone menu and check that:
|
|
131
|
+
|
|
132
|
+
- every formula sits inside its box (none at the top-left corner of the
|
|
133
|
+
diagram);
|
|
134
|
+
- the arrows meet the boxes;
|
|
135
|
+
- the widen button ↔ appears at 59% and 71% and goes at 50%;
|
|
136
|
+
- a preview card for the diagram is full size;
|
|
137
|
+
- printing the page (Share → Print) still shows the whole diagram.
|
|
138
|
+
|
|
139
|
+
## Known limits
|
|
140
|
+
|
|
141
|
+
- Text at 50% is 6.8 px on screen (13.6 × 0.5). That is the cost of the step,
|
|
142
|
+
and the reader's choice; there is no floor for a diagram.
|
|
143
|
+
- A diagram wider than the column after scaling still scrolls, and the widen
|
|
144
|
+
button still applies. The expanded view is capped to the viewport as before.
|
|
145
|
+
- Boxes and text shrink together, so the drawing keeps its proportions; the
|
|
146
|
+
layout is not recomputed at the smaller size.
|
|
147
|
+
- A saved size from the earlier steps (90, 80, 70) is ignored once: the page
|
|
148
|
+
opens at 100% until the reader picks again.
|