explico 0.13.0 → 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.
Files changed (239) hide show
  1. package/CHANGELOG.md +894 -1
  2. package/README.md +200 -30
  3. package/dist/compiler/astro.d.ts +15 -2
  4. package/dist/compiler/astro.js +40 -11
  5. package/dist/compiler/cli-main.d.ts +43 -0
  6. package/dist/compiler/cli-main.js +392 -0
  7. package/dist/compiler/cli.d.ts +424 -9
  8. package/dist/compiler/cli.js +1289 -8
  9. package/dist/compiler/collections.d.ts +88 -1
  10. package/dist/compiler/collections.js +170 -25
  11. package/dist/compiler/course-config.d.ts +1 -1
  12. package/dist/compiler/course-config.js +105 -1
  13. package/dist/compiler/decks.d.ts +161 -0
  14. package/dist/compiler/decks.js +235 -0
  15. package/dist/compiler/exports/combined-html.d.ts +5 -1
  16. package/dist/compiler/exports/deck-fit.d.ts +55 -0
  17. package/dist/compiler/exports/deck-fit.js +90 -0
  18. package/dist/compiler/exports/export-chapters.d.ts +37 -6
  19. package/dist/compiler/exports/export-chapters.js +64 -5
  20. package/dist/compiler/exports/export-pages.d.ts +30 -3
  21. package/dist/compiler/exports/export-pages.js +24 -1
  22. package/dist/compiler/exports/latex/beamerthemeexplico.sty +124 -0
  23. package/dist/compiler/exports/latex/cache.d.ts +78 -0
  24. package/dist/compiler/exports/latex/cache.js +129 -0
  25. package/dist/compiler/exports/latex/capture.d.ts +10 -1
  26. package/dist/compiler/exports/latex/capture.js +3 -1
  27. package/dist/compiler/exports/latex/compile.d.ts +13 -2
  28. package/dist/compiler/exports/latex/compile.js +12 -1
  29. package/dist/compiler/exports/latex/deck.d.ts +92 -0
  30. package/dist/compiler/exports/latex/deck.js +349 -0
  31. package/dist/compiler/exports/latex/document.d.ts +51 -6
  32. package/dist/compiler/exports/latex/document.js +71 -12
  33. package/dist/compiler/exports/latex/explico-common.sty +198 -0
  34. package/dist/compiler/exports/latex/explico.cls +13 -183
  35. package/dist/compiler/exports/latex/serialize.d.ts +60 -4
  36. package/dist/compiler/exports/latex/serialize.js +4 -2
  37. package/dist/compiler/exports/latex-export.d.ts +24 -2
  38. package/dist/compiler/exports/latex-export.js +107 -26
  39. package/dist/compiler/exports/markdown/serialize.d.ts +24 -4
  40. package/dist/compiler/exports/markdown/serialize.js +85 -8
  41. package/dist/compiler/exports/markdown-export.d.ts +6 -1
  42. package/dist/compiler/exports/markdown-export.js +50 -11
  43. package/dist/compiler/exports/standalone-html.d.ts +10 -5
  44. package/dist/compiler/exports/standalone-html.js +3 -3
  45. package/dist/compiler/exports/static-tree.d.ts +38 -1
  46. package/dist/compiler/exports/static-tree.js +120 -4
  47. package/dist/compiler/knowledge-version.d.ts +26 -2
  48. package/dist/compiler/knowledge-version.js +27 -17
  49. package/dist/compiler/lesson-derivation.d.ts +143 -6
  50. package/dist/compiler/lesson-derivation.js +145 -13
  51. package/dist/compiler/manifest.d.ts +133 -4
  52. package/dist/compiler/manifest.js +359 -58
  53. package/dist/compiler/page-versions.d.ts +160 -0
  54. package/dist/compiler/page-versions.js +198 -0
  55. package/dist/compiler/render-diagnostics.d.ts +5 -1
  56. package/dist/compiler/resolution-report.d.ts +24 -6
  57. package/dist/compiler/resolution-report.js +22 -4
  58. package/dist/compiler/validate-content.d.ts +7 -0
  59. package/dist/compiler/validate-content.js +160 -0
  60. package/dist/{course-config-rRtME5UV.d.ts → course-config-BLtIdkei.d.ts} +164 -1
  61. package/dist/curriculum/competency-graph.d.ts +54 -0
  62. package/dist/curriculum/competency-graph.js +41 -0
  63. package/dist/reference/base-library.d.mts +8 -0
  64. package/dist/reference/base-library.mjs +19 -0
  65. package/dist/reference/consistency.d.ts +8 -4
  66. package/dist/reference/consistency.js +76 -26
  67. package/dist/reference/deck-remark.d.mts +123 -0
  68. package/dist/reference/deck-remark.mjs +600 -0
  69. package/dist/reference/deck-source.d.ts +107 -0
  70. package/dist/reference/deck-source.js +140 -0
  71. package/dist/reference/decks-validate.d.ts +133 -0
  72. package/dist/reference/decks-validate.js +359 -0
  73. package/dist/reference/derived-entries.d.ts +5 -1
  74. package/dist/reference/diagram-fit.d.ts +1 -1
  75. package/dist/reference/figure-symbols.d.ts +23 -6
  76. package/dist/reference/figure-symbols.js +51 -15
  77. package/dist/reference/float-name.d.ts +43 -0
  78. package/dist/reference/float-name.js +58 -0
  79. package/dist/reference/folds-validate.d.ts +1 -1
  80. package/dist/reference/gate-abbreviations.d.ts +1 -1
  81. package/dist/reference/gate-indices.js +5 -4
  82. package/dist/reference/gate-math.d.ts +29 -2
  83. package/dist/reference/gate-math.js +99 -18
  84. package/dist/reference/gate-part-pages.d.ts +1 -1
  85. package/dist/reference/lab-katex-validate.d.ts +10 -6
  86. package/dist/reference/lab-katex-validate.js +17 -9
  87. package/dist/reference/lab-math-scope.js +29 -4
  88. package/dist/reference/math-glyphs.d.mts +25 -0
  89. package/dist/reference/math-glyphs.mjs +189 -25
  90. package/dist/reference/page-notation.d.ts +22 -3
  91. package/dist/reference/page-notation.js +32 -10
  92. package/dist/reference/references.d.ts +14 -1
  93. package/dist/reference/references.js +13 -0
  94. package/dist/reference/registry-scope.d.mts +33 -0
  95. package/dist/reference/registry-scope.mjs +34 -0
  96. package/dist/reference/registry.js +29 -10
  97. package/dist/reference/rehype-deck.d.mts +6 -0
  98. package/dist/reference/rehype-deck.mjs +286 -0
  99. package/dist/reference/rehype-heading-anchors.d.mts +20 -0
  100. package/dist/reference/rehype-heading-anchors.mjs +48 -6
  101. package/dist/reference/remark-notation.mjs +249 -40
  102. package/dist/reference/source-lines.d.mts +12 -0
  103. package/dist/reference/source-lines.mjs +65 -0
  104. package/dist/reference/types.d.ts +28 -0
  105. package/dist/rules/catalog.d.ts +21 -4
  106. package/dist/rules/catalog.js +221 -2
  107. package/dist/rules/course-rules.d.ts +5 -1
  108. package/dist/rules/course-rules.js +17 -3
  109. package/dist/rules/course-rules.schema.json +12 -0
  110. package/dist/rules/in-effect.d.ts +13 -3
  111. package/dist/rules/in-effect.js +30 -7
  112. package/dist/rules/rules.schema.json +259 -1
  113. package/dist/rules/settings.d.ts +1 -1
  114. package/dist/session/PreferenceStore.d.ts +6 -0
  115. package/dist/session/PreferenceStore.js +18 -1
  116. package/dist/session/deck-memory.d.ts +116 -0
  117. package/dist/session/deck-memory.js +143 -0
  118. package/dist/session/reading-memory.d.ts +96 -0
  119. package/dist/session/reading-memory.js +133 -0
  120. package/docs/VERSIONING.md +21 -18
  121. package/docs/authoring/README.md +29 -9
  122. package/docs/authoring/agents.md +63 -16
  123. package/docs/authoring/components.md +15 -0
  124. package/docs/authoring/deck.md +588 -0
  125. package/docs/authoring/migrating.md +1087 -14
  126. package/docs/authoring/notation.md +49 -6
  127. package/docs/authoring/structure.md +80 -1
  128. package/docs/authoring/versioning.md +54 -2
  129. package/docs/authoring/visualizations.md +7 -2
  130. package/docs/course-agent/AGENTS.md +37 -0
  131. package/docs/course-agent/agents/rules-reviewer.md +33 -0
  132. package/docs/course-agent/skills/draft-lesson/SKILL.md +37 -0
  133. package/docs/course-agent/skills/migrate-course/SKILL.md +31 -0
  134. package/docs/course-agent/skills/release-content/SKILL.md +24 -0
  135. package/docs/diagram-scaling.md +148 -0
  136. package/docs/privacy.md +59 -0
  137. package/package.json +8 -2
  138. package/src/compiler/astro.ts +60 -21
  139. package/src/compiler/cli-main.ts +469 -0
  140. package/src/compiler/cli.ts +1905 -16
  141. package/src/compiler/collections.ts +284 -25
  142. package/src/compiler/course-config.ts +267 -0
  143. package/src/compiler/decks.ts +455 -0
  144. package/src/compiler/exports/deck-fit.ts +127 -0
  145. package/src/compiler/exports/export-chapters.ts +105 -6
  146. package/src/compiler/exports/export-pages.ts +46 -2
  147. package/src/compiler/exports/latex/beamerthemeexplico.sty +124 -0
  148. package/src/compiler/exports/latex/cache.ts +183 -0
  149. package/src/compiler/exports/latex/capture.ts +5 -2
  150. package/src/compiler/exports/latex/compile.ts +18 -0
  151. package/src/compiler/exports/latex/deck.ts +419 -0
  152. package/src/compiler/exports/latex/document.ts +117 -21
  153. package/src/compiler/exports/latex/explico-common.sty +198 -0
  154. package/src/compiler/exports/latex/explico.cls +13 -183
  155. package/src/compiler/exports/latex/serialize.ts +25 -21
  156. package/src/compiler/exports/latex/texlive-packages.txt +6 -2
  157. package/src/compiler/exports/latex-export.ts +187 -45
  158. package/src/compiler/exports/markdown/serialize.ts +118 -9
  159. package/src/compiler/exports/markdown-export.ts +56 -11
  160. package/src/compiler/exports/standalone-html.ts +8 -7
  161. package/src/compiler/exports/static-tree.ts +175 -3
  162. package/src/compiler/knowledge-version.ts +56 -21
  163. package/src/compiler/lesson-derivation.ts +282 -16
  164. package/src/compiler/manifest.ts +558 -63
  165. package/src/compiler/page-versions.ts +359 -0
  166. package/src/compiler/resolution-report.ts +38 -7
  167. package/src/compiler/validate-content.ts +212 -0
  168. package/src/components/CourseContents.astro +124 -6
  169. package/src/components/CurriculumMap.astro +49 -1
  170. package/src/components/FloatIndex.astro +1 -1
  171. package/src/components/PartOverview.astro +51 -5
  172. package/src/components/assessments/AssessmentSet.astro +41 -15
  173. package/src/components/block-heading.ts +15 -6
  174. package/src/components/card-carry.ts +7 -4
  175. package/src/components/card-manager.ts +14 -2
  176. package/src/components/card-memory.ts +13 -9
  177. package/src/components/deck/DeckEnhancer.astro +13 -0
  178. package/src/components/deck/deck-context.ts +104 -0
  179. package/src/components/deck/deck-controller.ts +1287 -0
  180. package/src/components/deck/deck-map.ts +91 -0
  181. package/src/components/deck-lane.ts +163 -0
  182. package/src/components/diagram/Diagram.astro +131 -86
  183. package/src/components/disclosure/persisted-disclosure.ts +9 -1
  184. package/src/components/figure/Figure.astro +7 -2
  185. package/src/components/figure/FloatCaption.astro +5 -3
  186. package/src/components/fold/Fold.astro +2 -2
  187. package/src/components/notation/NotationFormula.astro +81 -11
  188. package/src/components/notation/NotationGlossary.astro +132 -1
  189. package/src/components/notation/NotationLayer.astro +29 -0
  190. package/src/components/starlight/AboutPanel.astro +186 -3
  191. package/src/components/starlight/CollapsibleSections.astro +24 -3
  192. package/src/components/starlight/DiagramEnhancer.astro +5 -1
  193. package/src/components/starlight/HeadingAnchorEnhancer.astro +1 -1
  194. package/src/components/starlight/LayoutHeader.astro +17 -0
  195. package/src/components/starlight/LayoutPageTitle.astro +110 -0
  196. package/src/components/starlight/LayoutSidebar.astro +807 -21
  197. package/src/components/starlight/LayoutSocialIcons.astro +62 -6
  198. package/src/components/starlight/LessonExportActions.astro +27 -9
  199. package/src/components/starlight/LessonFooter.astro +59 -0
  200. package/src/components/starlight/ReadingMemoryNotice.astro +114 -0
  201. package/src/components/starlight/TextSizeControl.astro +24 -13
  202. package/src/components/starlight/page-marks.ts +139 -0
  203. package/src/components/starlight/section-summary.ts +1 -1
  204. package/src/components/starlight/sidebar-editions.ts +245 -0
  205. package/src/components/table/Table.astro +15 -7
  206. package/src/content-config.ts +87 -1
  207. package/src/curriculum/competency-graph.ts +95 -0
  208. package/src/reference/base-library.mjs +19 -0
  209. package/src/reference/consistency.ts +126 -39
  210. package/src/reference/deck-remark.mjs +600 -0
  211. package/src/reference/deck-source.ts +273 -0
  212. package/src/reference/decks-validate.ts +604 -0
  213. package/src/reference/diagram-layout.ts +3 -1
  214. package/src/reference/figure-symbols.ts +108 -31
  215. package/src/reference/folds-validate.ts +1 -1
  216. package/src/reference/gate-indices.ts +5 -3
  217. package/src/reference/gate-math.ts +169 -25
  218. package/src/reference/lab-katex-validate.ts +30 -15
  219. package/src/reference/lab-math-scope.ts +34 -3
  220. package/src/reference/math-glyphs.mjs +189 -25
  221. package/src/reference/page-notation.ts +69 -15
  222. package/src/reference/references.ts +28 -0
  223. package/src/reference/registry-scope.mjs +34 -0
  224. package/src/reference/registry.ts +41 -11
  225. package/src/reference/rehype-deck.mjs +286 -0
  226. package/src/reference/rehype-heading-anchors.mjs +48 -6
  227. package/src/reference/remark-notation.mjs +249 -40
  228. package/src/reference/source-lines.mjs +65 -0
  229. package/src/reference/types.ts +28 -0
  230. package/src/rules/catalog.ts +257 -4
  231. package/src/rules/course-rules.ts +21 -1
  232. package/src/rules/in-effect.ts +54 -8
  233. package/src/session/PreferenceStore.ts +23 -0
  234. package/src/session/deck-memory.ts +263 -0
  235. package/src/session/reading-memory.ts +243 -0
  236. package/src/styles/deck.css +1150 -0
  237. package/src/styles/global.css +958 -27
  238. package/src/styles/zones.css +277 -0
  239. /package/src/{components → reference}/float-name.ts +0 -0
@@ -132,12 +132,16 @@ type Block = {
132
132
  readonly ordered: boolean;
133
133
  readonly start?: number;
134
134
  readonly items: readonly (readonly Block[])[];
135
+ /** On a deck, the step each item appears at (1 for the first), when an item is a step of its own. */
136
+ readonly itemSteps?: readonly number[];
135
137
  } | {
136
138
  readonly kind: 'equation';
137
139
  readonly anchor: string;
138
140
  readonly number: string;
139
141
  readonly key?: string;
140
142
  readonly tex: string;
143
+ /** On a deck, a recalled equation's place in its chapter. */
144
+ readonly recall?: string;
141
145
  } | {
142
146
  readonly kind: 'displayMath';
143
147
  readonly tex: string;
@@ -249,5 +253,38 @@ declare function readDiagramGeometry(figure: HtmlElement): DiagramGeometry | und
249
253
  declare function readInlineFragment(html: string, route: string, base?: string): Inline[];
250
254
  /** Read the static reading tree of one built page. */
251
255
  declare function readStaticPage(html: string, slug: string, route: string): StaticPage;
256
+ /** One frame of a deck page (docs/authoring/deck.md), as print exports read it. */
257
+ interface DeckFrame {
258
+ readonly number: number;
259
+ readonly kind: 'title' | 'section' | 'frame' | 'outcomes' | 'references';
260
+ /** The frame heading's anchor. */
261
+ readonly anchor?: string;
262
+ readonly title: readonly Inline[];
263
+ /** The frame's content, a run of blocks per step, in reading order: step 1 is what shows first. */
264
+ readonly segments: readonly {
265
+ readonly step: number;
266
+ readonly blocks: readonly Block[];
267
+ }[];
268
+ readonly steps: number;
269
+ readonly notes: readonly Block[];
270
+ /** The legend line: each symbol's TeX and its name. */
271
+ readonly legend: readonly {
272
+ readonly tex: string;
273
+ readonly label: readonly Inline[];
274
+ }[];
275
+ }
276
+ interface StaticDeck {
277
+ readonly slug: string;
278
+ readonly route: string;
279
+ readonly title: string;
280
+ readonly frames: readonly DeckFrame[];
281
+ /**
282
+ * The page's notation list: its lesson's symbols, its chapter's included.
283
+ * The PDF's appendix keeps the ones its frames use.
284
+ */
285
+ readonly notation: readonly NotationEntry[];
286
+ }
287
+ /** Read the frames of one built deck page. */
288
+ declare function readStaticDeck(html: string, slug: string, route: string): StaticDeck;
252
289
 
253
- export { type Block, type CourseTarget, type DiagramGeometry, type DiagramNodeShape, type FigurePicture, type Inline, type NotationEntry, type RefKind, type StaticPage, type TableCell, courseTarget, readDiagramGeometry, readInlineFragment, readStaticPage, readableText };
290
+ export { type Block, type CourseTarget, type DeckFrame, type DiagramGeometry, type DiagramNodeShape, type FigurePicture, type Inline, type NotationEntry, type RefKind, type StaticDeck, type StaticPage, type TableCell, courseTarget, readDiagramGeometry, readInlineFragment, readStaticDeck, readStaticPage, readableText };
@@ -386,7 +386,8 @@ function readBlock(node, context) {
386
386
  anchor: attribute(node, "id") ?? "",
387
387
  number: attribute(node, "data-eq-number") ?? "",
388
388
  ...key ? { key } : {},
389
- tex: (katex && texOf(katex)) ?? ""
389
+ tex: (katex && texOf(katex)) ?? "",
390
+ ...attribute(node, "data-eq-recall") ? { recall: attribute(node, "data-eq-recall") } : {}
390
391
  }
391
392
  ];
392
393
  }
@@ -480,6 +481,18 @@ function readBlock(node, context) {
480
481
  if (node.tagName === "compact-examples" || hasClass(node, "compact-examples")) {
481
482
  return [readExamples(node, context)];
482
483
  }
484
+ if (hasClass(node, "deck-check__label") && attribute(node, "data-block-kind") === "check") {
485
+ const anchor = attribute(node, "id") ?? "";
486
+ return [
487
+ {
488
+ kind: "check",
489
+ anchor,
490
+ number: attribute(node, "data-check-number") ?? "",
491
+ assessmentId: anchor.replace(/^check-/u, ""),
492
+ title: [{ kind: "text", value: attribute(node, "data-check-title") ?? "" }]
493
+ }
494
+ ];
495
+ }
483
496
  if (hasClass(node, "sl-heading-wrapper")) {
484
497
  const heading = headingOf(node);
485
498
  if (!heading) return readBlocks(node, context);
@@ -599,6 +612,17 @@ function readStaticPage(html, slug, route) {
599
612
  const title = elements(main, (node) => node.tagName === "h1")[0];
600
613
  const content = elements(main, (node) => hasClass(node, "sl-markdown-content"))[0];
601
614
  if (!content) throw new Error(`Built page ${slug} has no .sl-markdown-content element.`);
615
+ const { notation, notationRemarks } = readPageNotation(main, context);
616
+ return {
617
+ slug,
618
+ route,
619
+ title: title ? collapse(readableText(title)).trim() : slug,
620
+ blocks: readBlocks(content, context),
621
+ notation,
622
+ notationRemarks
623
+ };
624
+ }
625
+ function readPageNotation(main, context) {
602
626
  const layer = elements(
603
627
  main,
604
628
  (node) => hasClass(node, "notation-layer") && attribute(node, "data-notation-layer-id") === "page-notation"
@@ -607,19 +631,111 @@ function readStaticPage(html, slug, route) {
607
631
  const remarks = layer ? elements(layer, (node) => node.tagName === "ul" && hasClass(node, "notation-suspended")).flatMap(
608
632
  (list) => childrenOf(list).filter((item) => isElement(item) && item.tagName === "li").map((item) => inlineContent(item, context))
609
633
  ) : [];
634
+ return { notation: entries.map((entry) => readNotationEntry(entry, context)), notationRemarks: remarks };
635
+ }
636
+ function listItemSteps(list) {
637
+ const items = childrenOf(list).filter((child) => isElement(child) && child.tagName === "li");
638
+ let step = 1;
639
+ let any = false;
640
+ const steps = items.map((item) => {
641
+ const at = attribute(item, "data-step");
642
+ if (at !== void 0) {
643
+ step = Number(at) || step;
644
+ any = true;
645
+ }
646
+ return step;
647
+ });
648
+ return any ? steps : void 0;
649
+ }
650
+ function readSegments(body, context) {
651
+ const segments = [];
652
+ const add = (step, blocks) => {
653
+ if (blocks.length === 0) return;
654
+ const last = segments.at(-1);
655
+ if (last && last.step === step) last.blocks.push(...blocks);
656
+ else segments.push({ step, blocks: [...blocks] });
657
+ };
658
+ let pending = [];
659
+ const flush = () => {
660
+ if (pending.length === 0) return;
661
+ const holder = { nodeName: "div", tagName: "div", attrs: [], childNodes: pending };
662
+ add(1, withItemSteps(holder, readBlocks(holder, context)));
663
+ pending = [];
664
+ };
665
+ for (const child of childrenOf(body)) {
666
+ if (isElement(child) && hasClass(child, "deck-step") && attribute(child, "data-step") !== void 0) {
667
+ flush();
668
+ add(Number(attribute(child, "data-step")) || 1, withItemSteps(child, readBlocks(child, context)));
669
+ continue;
670
+ }
671
+ pending.push(child);
672
+ }
673
+ flush();
674
+ return segments;
675
+ }
676
+ function withItemSteps(node, blocks) {
677
+ const lists = childrenOf(node).filter(
678
+ (child) => isElement(child) && (child.tagName === "ul" || child.tagName === "ol")
679
+ );
680
+ let index = 0;
681
+ return blocks.map((block) => {
682
+ if (block.kind !== "list") return block;
683
+ const list = lists[index++];
684
+ const itemSteps = list ? listItemSteps(list) : void 0;
685
+ return itemSteps ? { ...block, itemSteps } : block;
686
+ });
687
+ }
688
+ function readStaticDeck(html, slug, route) {
689
+ const document = parse(html);
690
+ const base = inferBasePrefix(document);
691
+ const context = { route, base };
692
+ const main = elements(document, (node) => node.tagName === "main")[0];
693
+ if (!main) throw new Error(`Built page ${slug} has no <main> element.`);
694
+ const deck = elements(main, (node) => hasClass(node, "deck") && attribute(node, "data-deck") !== void 0)[0];
695
+ if (!deck) throw new Error(`Built page ${slug} has no deck (.deck[data-deck]).`);
696
+ const title = elements(main, (node) => node.tagName === "h1")[0];
697
+ const frames = childrenOf(deck).filter((child) => isElement(child) && hasClass(child, "deck-frame")).map((frame) => {
698
+ const canvas = firstByClass(frame, "deck-canvas");
699
+ const heading = canvas ? firstByClass(canvas, "deck-frame__title") ?? firstByClass(canvas, "deck-title__title") : void 0;
700
+ const body = canvas ? firstByClass(canvas, "deck-frame__body") : void 0;
701
+ const notes = childrenOf(frame).find(
702
+ (child) => isElement(child) && hasClass(child, "deck-notes")
703
+ );
704
+ const legend = canvas ? firstByClass(canvas, "deck-legend") : void 0;
705
+ const kind = attribute(frame, "data-kind") ?? "frame";
706
+ const generatedContent = !body && canvas ? readBlocks(
707
+ canvas,
708
+ context,
709
+ (child) => !(isElement(child) && (hasClass(child, "deck-frame__title") || hasClass(child, "deck-frame__foot") || hasClass(child, "deck-title__title")))
710
+ ) : [];
711
+ return {
712
+ number: Number(attribute(frame, "data-frame")) || 0,
713
+ kind,
714
+ ...heading && attribute(heading, "id") ? { anchor: attribute(heading, "id") } : {},
715
+ title: heading ? inlineContent(heading, context) : [],
716
+ segments: body ? readSegments(body, context) : generatedContent.length > 0 ? [{ step: 1, blocks: generatedContent }] : [],
717
+ steps: Number(attribute(frame, "data-steps")) || 1,
718
+ notes: notes ? readBlocks(notes, context) : [],
719
+ legend: legend ? elements(legend, (node) => hasClass(node, "deck-legend__item")).map((item) => {
720
+ const katex = elements(item, (node) => hasClass(node, "katex"))[0];
721
+ const label = firstByClass(item, "deck-legend__label");
722
+ return { tex: (katex && texOf(katex)) ?? "", label: label ? inlineContent(label, context) : [] };
723
+ }) : []
724
+ };
725
+ });
610
726
  return {
611
727
  slug,
612
728
  route,
613
729
  title: title ? collapse(readableText(title)).trim() : slug,
614
- blocks: readBlocks(content, context),
615
- notation: entries.map((entry) => readNotationEntry(entry, context)),
616
- notationRemarks: remarks
730
+ frames,
731
+ notation: readPageNotation(main, context).notation
617
732
  };
618
733
  }
619
734
  export {
620
735
  courseTarget,
621
736
  readDiagramGeometry,
622
737
  readInlineFragment,
738
+ readStaticDeck,
623
739
  readStaticPage,
624
740
  readableText
625
741
  };
@@ -21,7 +21,13 @@ interface KnowledgeChangelogEntry {
21
21
  /** Bullet lines under the heading, marker stripped, in file order. */
22
22
  readonly notes: readonly string[];
23
23
  }
24
- type KnowledgeVersionCode = 'knowledge-changelog-missing' | 'knowledge-version-invalid' | 'knowledge-version-mismatch';
24
+ /** The released entries and, apart from them, the bullets `## [Unreleased]` holds. */
25
+ interface KnowledgeChangelog {
26
+ readonly entries: readonly KnowledgeChangelogEntry[];
27
+ /** Bullet lines under `## [Unreleased]`, any `###` group included, marker stripped. */
28
+ readonly unreleased: readonly string[];
29
+ }
30
+ type KnowledgeVersionCode = 'knowledge-changelog-missing' | 'knowledge-version-invalid' | 'knowledge-version-mismatch' | 'page-versions-invalid';
25
31
  interface KnowledgeVersionDiagnostic {
26
32
  readonly code: KnowledgeVersionCode;
27
33
  /** Tier 3 — a knowledge-version drift never fails the build. */
@@ -34,9 +40,27 @@ interface KnowledgeVersion {
34
40
  readonly version: string;
35
41
  /** The newest released `## [x.y.z]` entry, when the changelog has one. */
36
42
  readonly latest?: KnowledgeChangelogEntry;
43
+ /** Every released entry, newest first; empty without a changelog. */
44
+ readonly entries: readonly KnowledgeChangelogEntry[];
45
+ /**
46
+ * The bullets `## [Unreleased]` holds: a content change not yet released
47
+ * (rule C1). A page changed while there are any is "not yet released"
48
+ * (rule C5); with none, a change is the release's own, or one rule C1 lets
49
+ * go unlisted.
50
+ */
51
+ readonly unreleased: readonly string[];
37
52
  readonly diagnostics: readonly KnowledgeVersionDiagnostic[];
38
53
  }
39
54
  declare function isKnowledgeSemver(value: string): boolean;
55
+ /**
56
+ * A `content/CHANGELOG.md` body: every released entry in file order (newest
57
+ * first, by Keep a Changelog convention), and the bullets of
58
+ * `## [Unreleased]`. A released entry's notes end at a `###` group heading,
59
+ * as the About panel has always shown them; the unreleased bullets are
60
+ * counted through every group, since what they answer is whether anything
61
+ * is waiting for a release.
62
+ */
63
+ declare function scanKnowledgeChangelog(text: string): KnowledgeChangelog;
40
64
  /**
41
65
  * Every released entry in a `content/CHANGELOG.md` body, in file order (newest
42
66
  * first, by Keep a Changelog convention). `## [Unreleased]` is skipped.
@@ -55,4 +79,4 @@ declare function resolveKnowledgeVersion(options: {
55
79
  readonly file?: string;
56
80
  }): KnowledgeVersion;
57
81
 
58
- export { type KnowledgeChangelogEntry, type KnowledgeVersion, type KnowledgeVersionCode, type KnowledgeVersionDiagnostic, isKnowledgeSemver, parseKnowledgeChangelog, resolveKnowledgeVersion };
82
+ export { type KnowledgeChangelog, type KnowledgeChangelogEntry, type KnowledgeVersion, type KnowledgeVersionCode, type KnowledgeVersionDiagnostic, isKnowledgeSemver, parseKnowledgeChangelog, resolveKnowledgeVersion, scanKnowledgeChangelog };
@@ -4,18 +4,19 @@ const BULLET_RE = /^[-*]\s+(.*\S)\s*$/;
4
4
  function isKnowledgeSemver(value) {
5
5
  return SEMVER_RE.test(value.trim());
6
6
  }
7
- function parseKnowledgeChangelog(text) {
7
+ function scanKnowledgeChangelog(text) {
8
8
  const lines = text.split(/\r?\n/);
9
9
  const entries = [];
10
+ const unreleased = [];
10
11
  let current = null;
12
+ let inUnreleased = false;
11
13
  for (const line of lines) {
12
14
  const heading = HEADING_RE.exec(line);
13
15
  if (heading) {
14
16
  const token = (heading[1] ?? "").trim();
15
- if (/^unreleased$/i.test(token)) {
16
- current = null;
17
- continue;
18
- }
17
+ current = null;
18
+ inUnreleased = /^unreleased$/i.test(token);
19
+ if (inUnreleased) continue;
19
20
  current = { version: token, notes: [] };
20
21
  const date = (heading[2] ?? "").trim();
21
22
  if (date) current.date = date;
@@ -24,17 +25,25 @@ function parseKnowledgeChangelog(text) {
24
25
  }
25
26
  if (/^#{1,3}\s/.test(line) && !line.startsWith("## [")) {
26
27
  current = null;
28
+ if (/^#{1,2}\s/.test(line)) inUnreleased = false;
27
29
  continue;
28
30
  }
29
- if (!current) continue;
30
31
  const bullet = BULLET_RE.exec(line);
31
- if (bullet) current.notes.push(bullet[1] ?? "");
32
+ if (!bullet) continue;
33
+ if (current) current.notes.push(bullet[1] ?? "");
34
+ else if (inUnreleased) unreleased.push(bullet[1] ?? "");
32
35
  }
33
- return entries.map((entry) => ({
34
- version: entry.version,
35
- ...entry.date === void 0 ? {} : { date: entry.date },
36
- notes: [...entry.notes]
37
- }));
36
+ return {
37
+ entries: entries.map((entry) => ({
38
+ version: entry.version,
39
+ ...entry.date === void 0 ? {} : { date: entry.date },
40
+ notes: [...entry.notes]
41
+ })),
42
+ unreleased
43
+ };
44
+ }
45
+ function parseKnowledgeChangelog(text) {
46
+ return [...scanKnowledgeChangelog(text).entries];
38
47
  }
39
48
  function resolveKnowledgeVersion(options) {
40
49
  const { changelogText, declaredVersion } = options;
@@ -54,16 +63,16 @@ function resolveKnowledgeVersion(options) {
54
63
  "knowledge-changelog-missing",
55
64
  `No ${file}. Add a Keep a Changelog file whose newest \`## [x.y.z]\` heading is ${JSON.stringify(declaredVersion)} (rule C1).`
56
65
  );
57
- return { version: declaredVersion, diagnostics };
66
+ return { version: declaredVersion, entries: [], unreleased: [], diagnostics };
58
67
  }
59
- const entries = parseKnowledgeChangelog(changelogText);
68
+ const { entries, unreleased } = scanKnowledgeChangelog(changelogText);
60
69
  const latest = entries[0];
61
70
  if (!latest) {
62
71
  warn(
63
72
  "knowledge-changelog-missing",
64
73
  `${file} has no released \`## [x.y.z]\` entry \u2014 only \`## [Unreleased]\`. Cut a release: move it to \`## [${declaredVersion}] - <date>\` (rule C3).`
65
74
  );
66
- return { version: declaredVersion, diagnostics };
75
+ return { version: declaredVersion, entries, unreleased, diagnostics };
67
76
  }
68
77
  if (isKnowledgeSemver(declaredVersion) && latest.version !== declaredVersion) {
69
78
  warn(
@@ -71,10 +80,11 @@ function resolveKnowledgeVersion(options) {
71
80
  `about.version is ${JSON.stringify(declaredVersion)} but the newest ${file} entry is ${JSON.stringify(latest.version)}. Bump one to match the other (rule C3).`
72
81
  );
73
82
  }
74
- return { version: declaredVersion, latest, diagnostics };
83
+ return { version: declaredVersion, latest, entries, unreleased, diagnostics };
75
84
  }
76
85
  export {
77
86
  isKnowledgeSemver,
78
87
  parseKnowledgeChangelog,
79
- resolveKnowledgeVersion
88
+ resolveKnowledgeVersion,
89
+ scanKnowledgeChangelog
80
90
  };
@@ -23,14 +23,62 @@
23
23
  * shared keys the body names with `[[key]]` / `\explain` — so an import list
24
24
  * is redundant.
25
25
  */
26
- /** `foundations/discount-factors` → `foundations.discount-factors`. */
26
+ /**
27
+ * `foundations/discount-factors` → `foundations.discount-factors`. A deck
28
+ * file's slug (`foundations/discount-factors.deck.mdx`) gives its lesson's id;
29
+ * a deck's route (`foundations/discount-factors/slides`) gives the deck's page
30
+ * id, `foundations.discount-factors.slides` ({@link deckPageId}).
31
+ */
27
32
  declare function lessonIdFromSlug(slug: string): string;
28
33
  /**
29
- * A docs entry is a lesson when it lives in a section directory. The three
34
+ * A docs entry is a chapter when it lives in a section directory. The three
30
35
  * top-level pages (`index`, `curriculum-map`, `glossary`) are not lessons; a
31
- * future `<section>/index` page would not be one either.
36
+ * future `<section>/index` page would not be one either, and neither is a
37
+ * lesson's deck file (`<lesson>.deck.mdx`, {@link isDeckSlug}), which is an
38
+ * edition of a lesson rather than a lesson.
32
39
  */
33
40
  declare function isLessonSlug(slug: string): boolean;
41
+ /**
42
+ * The last segment of a deck's route when the deck stands beside its chapter:
43
+ * `projectiles/projectile-range` is the chapter, `projectiles/projectile-range/slides`
44
+ * the deck.
45
+ */
46
+ declare const DECK_ROUTE_SEGMENT = "slides";
47
+ /**
48
+ * A deck file — a lesson's slide edition, `content/docs/<part>/<lesson>.deck.mdx`
49
+ * beside the chapter `<lesson>.mdx` or alone for a lesson that is only slides,
50
+ * or a part's deck ({@link isPartDeckSlug}). True for the docs slug with or
51
+ * without its extension; a caller that needs the lesson asks
52
+ * {@link isPartDeckSlug} first.
53
+ */
54
+ declare function isDeckSlug(slug: string): boolean;
55
+ /** The lesson a lesson's deck file belongs to: `motion/range.deck.mdx` → `motion/range`. */
56
+ declare function deckLessonSlug(slug: string): string;
57
+ /**
58
+ * A part's deck, `content/docs/<part>/index.deck.mdx` beside the part's page
59
+ * `index.mdx`: the slides of the part, served at `/<part>/slides/`
60
+ * ({@link partDeckRouteSlug}), whose `##` sections may each name the chapter
61
+ * they cover (`docs/authoring/deck.md`). It is no lesson's edition.
62
+ */
63
+ declare function isPartDeckSlug(slug: string): boolean;
64
+ /** The part a part's deck belongs to: `motion/index.deck.mdx` → `motion`. */
65
+ declare function partDeckDir(slug: string): string;
66
+ /** Where a part's deck is served: `<part>/slides`, as a chapter's deck is `<lesson>/slides`. */
67
+ declare function partDeckRouteSlug(partDir: string): string;
68
+ /** The page id of a part's deck, the id of its route: `<part>.slides`. */
69
+ declare function partDeckPageId(partDir: string): string;
70
+ /**
71
+ * Where a lesson's deck is served: `<lesson>/slides` beside its chapter, or
72
+ * the lesson's own route when the lesson has no chapter (a slides-first
73
+ * lesson, whose deck is its only edition).
74
+ */
75
+ declare function deckRouteSlug(lessonSlug: string, hasChapter: boolean): string;
76
+ /**
77
+ * The page id a deck's notation bundle, gates and diagnostics use — the id of
78
+ * its route ({@link lessonIdFromSlug}): `<lessonId>.slides` beside a chapter,
79
+ * the lesson's own id for a slides-first lesson.
80
+ */
81
+ declare function deckPageId(lessonId: string, hasChapter: boolean): string;
34
82
  /**
35
83
  * The docs slugs a Starlight route id can stand for. The glob loader shortens
36
84
  * `<dir>/index.mdx` to the id `<dir>`, and may give the landing page an empty
@@ -85,12 +133,21 @@ interface SidebarSection {
85
133
  /**
86
134
  * One sidebar entry: a lesson slug, or an explicit link. `attrs` is Starlight's
87
135
  * own passthrough onto the rendered `<a>` — the engine uses it to mark a part's
88
- * overview row so CSS can give it its own icon.
136
+ * overview row so CSS can give it its own icon, and to name each edition of a
137
+ * lesson for the sidebar's script ({@link buildSidebar}).
89
138
  */
90
139
  type SidebarItem = string | {
91
140
  readonly label: string;
92
141
  readonly link: string;
93
142
  readonly attrs?: Readonly<Record<string, string>>;
143
+ } | {
144
+ /**
145
+ * A docs entry by its id (Starlight's `slug` item), with its own label,
146
+ * or the entry's title without one.
147
+ */
148
+ readonly slug: string;
149
+ readonly label?: string;
150
+ readonly attrs?: Readonly<Record<string, string>>;
94
151
  };
95
152
  interface SidebarGroup {
96
153
  readonly label: string;
@@ -98,15 +155,95 @@ interface SidebarGroup {
98
155
  }
99
156
  /** Marks the overview row so `global.css` can give it its own icon. */
100
157
  declare const PART_OVERVIEW_CLASS = "explico-part-overview";
158
+ /**
159
+ * Marks a deck's row, listed under its chapter, so `deck.css` can set it
160
+ * apart. That is the sidebar without its script; with it, the deck is its
161
+ * chapter row's trailing icon, or leads the row (`sidebar-editions.ts`).
162
+ */
163
+ declare const DECK_ROW_CLASS = "explico-deck-row";
164
+ /**
165
+ * Marks a slides-first lesson's row, whose deck is the lesson, so `deck.css`
166
+ * gives it the deck's glyph where a chapter has none (a row) or a sheet (a
167
+ * compact tile). It is a lesson of the reading order, unlike {@link DECK_ROW_CLASS}.
168
+ */
169
+ declare const DECK_LESSON_CLASS = "explico-deck-lesson";
170
+ /**
171
+ * Marks a part's deck, listed right after the part's Overview (with
172
+ * {@link DECK_ROW_CLASS}, so Next and Previous step past it as past any
173
+ * deck): the part's own, as Overview is, with the part's deck glyph. The
174
+ * sidebar's script makes the two one row, the part's, as a lesson's chapter
175
+ * and deck are one (`sidebar-editions.ts`).
176
+ */
177
+ declare const PART_DECK_CLASS = "explico-part-deck";
178
+ /**
179
+ * Numbers as a reader reads a run of them: `1–2`, `1, 3`, `2–4, 6`. A run of
180
+ * two or more is its ends with an en dash.
181
+ */
182
+ declare function numberList(numbers: readonly number[]): string;
183
+ /**
184
+ * The lesson a sidebar item lists, by its slug: an item served at its
185
+ * lesson's own URL (its `data-lesson` is its `slug`), a chapter or a
186
+ * slides-first lesson's deck. A part's overview, a chapter's slides row
187
+ * (served under the chapter) and a link list no lesson. A bare slug is a
188
+ * lesson too.
189
+ */
190
+ declare function sidebarLessonSlug(item: SidebarItem): string | undefined;
101
191
  /**
102
192
  * The Starlight `sidebar` config: one group per section, lessons ordered by the
103
193
  * merged track order (ties broken by slug), then the static Reference group.
104
194
  * Replaces seven `autogenerate` blocks that read an authored `sidebar.order`.
105
195
  * `floatIndex`: the course has a top-level "Figures and tables" page
106
196
  * (`figures-and-tables.mdx`, `FloatIndex.astro`), listed in Reference.
197
+ *
198
+ * Each edition of a lesson is an item of its own, its chapter first, and
199
+ * names its edition, lesson and title (`data-edition`, `data-lesson`,
200
+ * `data-title` on the link): the HTML lists both, and the sidebar's script
201
+ * (`LayoutSidebar.astro`, `sidebar-editions.ts`) makes the two one row.
202
+ *
203
+ * A part's deck is an item of the part right after its Overview. Both name
204
+ * their part and which of its two editions they are (`data-part`,
205
+ * `data-part-edition`), and the deck its part's label and the ordinals of
206
+ * the chapters it covers (`data-title`, `data-chapters`: `1–2`), so the
207
+ * script makes them the part's one row. A chapter it covers that has no deck
208
+ * of its own carries the address of its section in the part's deck
209
+ * (`data-deck-href`, `/<part>/slides/#slide-N`, without the site's base),
210
+ * which the script makes the chapter's other edition: a lesson's own deck
211
+ * wins over its part's. The frames the deck and each section hold are
212
+ * `buildDecks`'s, added once it has run ({@link withPartDeckFrames}).
107
213
  */
108
- declare function buildSidebar(tracks: readonly TrackLike[], lessonSlugs: readonly string[], parts: readonly SidebarSection[], { floatIndex }?: {
214
+ declare function buildSidebar(tracks: readonly TrackLike[], lessonSlugs: readonly string[], parts: readonly SidebarSection[], { floatIndex, decks, slidesFirst, partDecks, titleOf, }?: {
109
215
  readonly floatIndex?: boolean;
216
+ /** A deck beside its chapter: chapter slug → the deck's route, listed under the chapter. */
217
+ readonly decks?: ReadonlyMap<string, string>;
218
+ /** The lessons whose only edition is a deck, by slug: listed with the deck's glyph. */
219
+ readonly slidesFirst?: ReadonlySet<string>;
220
+ /**
221
+ * A part's deck, by the part's directory: its route, and the frame each
222
+ * chapter it covers opens on (its section's divider), by chapter slug.
223
+ */
224
+ readonly partDecks?: ReadonlyMap<string, {
225
+ readonly route: string;
226
+ readonly covers: ReadonlyMap<string, number>;
227
+ }>;
228
+ /** A lesson's title, by its slug: `data-title` on each of its items, left out when unknown. */
229
+ readonly titleOf?: (slug: string) => string | undefined;
110
230
  }): SidebarGroup[];
231
+ /** A part's deck as {@link withPartDeckFrames} reads it: its part and its frames, each with the chapter it covers. */
232
+ interface PartDeckFrames {
233
+ readonly part?: string;
234
+ readonly frames: readonly {
235
+ readonly number: number;
236
+ readonly chapter?: string;
237
+ }[];
238
+ }
239
+ /**
240
+ * The sidebar with each part's deck's frames, as `buildDecks` numbers them:
241
+ * the deck's count on its item (`data-frames`), and on each chapter that
242
+ * links into its section the frames that section holds (`data-deck-range`,
243
+ * `2–16`, or `5` for one), its first to its last when it runs over several
244
+ * sections. `buildDecks` runs after the sidebar is built, since the chapters
245
+ * it numbers are ordered by the sidebar.
246
+ */
247
+ declare function withPartDeckFrames(groups: readonly SidebarGroup[], decks: readonly PartDeckFrames[]): SidebarGroup[];
111
248
 
112
- export { PART_OVERVIEW_CLASS, type SidebarGroup, type SidebarItem, type SidebarSection, buildSidebar, citedSourceIds, deriveRequires, deriveSources, docsSlugsForRoute, isLessonSlug, lessonIdFromSlug, mergeTrackOrder, parseCheckList };
249
+ export { DECK_LESSON_CLASS, DECK_ROUTE_SEGMENT, DECK_ROW_CLASS, PART_DECK_CLASS, PART_OVERVIEW_CLASS, type PartDeckFrames, type SidebarGroup, type SidebarItem, type SidebarSection, buildSidebar, citedSourceIds, deckLessonSlug, deckPageId, deckRouteSlug, deriveRequires, deriveSources, docsSlugsForRoute, isDeckSlug, isLessonSlug, isPartDeckSlug, lessonIdFromSlug, mergeTrackOrder, numberList, parseCheckList, partDeckDir, partDeckPageId, partDeckRouteSlug, sidebarLessonSlug, withPartDeckFrames };