@brandon_m_behring/book-scaffold-astro 4.24.0 → 4.25.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/dist/index.d.ts CHANGED
@@ -1,11 +1,11 @@
1
- import { AstroUserConfig, AstroIntegration } from 'astro';
2
- import { c as BookConfigOptions, f as BookScaffoldIntegrationOptions, h as ChaptersRenderer, l as Style } from './types-CGN95mrX.js';
3
- export { B as BOOK_PRESETS, a as BOOK_PROFILES, b as BookConfigError, d as BookPreset, e as BookProfile, g as BookSchemasOptions, C as ChapterFor, F as FreshnessAffordance, i as FrontmatterRouteConfig, P as PartKey, j as PartialRouteToggles, k as ProfileDefinition, R as RouteToggles, S as StatusBadge, m as StyleInput, V as VolatilityBadge, n as composeStyles, o as defineProfile, p as defineStyle, q as normalizeFrontmatterConfig, r as resolvePreset, s as resolveProfile } from './types-CGN95mrX.js';
4
- import { D as volatilityLevels, c as academicParts, Q as Question } from './schemas-DDWDRUxs.js';
5
- export { A as AcademicChapter, B as BloomLevel, C as CourseNotesChapter, G as GlossaryTerm, M as MinimalChapter, P as Provenance, a as QuestionType, R as ResearchPortfolioChapter, T as ToolsChapter, b as academicChapterSchema, d as bloomLevels, e as changeKinds, f as changelogSchema, g as chapterStatus, h as citationBackstops, i as courseNotesChapterSchema, j as glossarySchema, m as minimalChapterSchema, p as patternCategories, k as patternsSchema, l as provenanceObject, n as provenanceSchema, q as questionDifficulties, o as questionSchema, r as questionTypes, s as refineQuestion, t as refinedQuestionSchema, u as researchPortfolioChapterSchema, v as sourceTiers, w as sourceTiersResearch, x as sourcesSchema, y as toolSlugs, z as toolsChapterSchema } from './schemas-DDWDRUxs.js';
1
+ import { AstroUserConfig, AstroIntegration, MarkdownHeading } from 'astro';
2
+ import { c as BookConfigOptions, f as BookScaffoldIntegrationOptions, h as ChaptersRenderer, l as Style } from './types-Bn7rKhgP.js';
3
+ export { B as BOOK_PRESETS, a as BOOK_PROFILES, b as BookConfigError, d as BookPreset, e as BookProfile, g as BookSchemasOptions, C as ChapterFor, F as FreshnessAffordance, i as FrontmatterRouteConfig, P as PartKey, j as PartialRouteToggles, k as ProfileDefinition, R as RouteToggles, S as StatusBadge, m as StyleInput, V as VolatilityBadge, n as composeStyles, o as defineProfile, p as defineStyle, q as normalizeFrontmatterConfig, r as resolvePreset, s as resolveProfile } from './types-Bn7rKhgP.js';
4
+ import { E as volatilityLevels, c as academicParts, Q as Question } from './schemas-CKipJ5Ie.js';
5
+ export { A as AcademicChapter, B as BloomLevel, C as CourseNotesChapter, G as GlossaryTerm, M as MinimalChapter, P as Provenance, a as QuestionType, R as ResearchPortfolioChapter, T as ToolsChapter, b as academicChapterSchema, d as bloomLevels, e as changeKinds, f as changelogSchema, g as chapterStatus, h as citationBackstops, i as courseNotesChapterSchema, j as glossarySchema, l as layoutModes, m as minimalChapterSchema, p as patternCategories, k as patternsSchema, n as provenanceObject, o as provenanceSchema, q as questionDifficulties, r as questionSchema, s as questionTypes, t as refineQuestion, u as refinedQuestionSchema, v as researchPortfolioChapterSchema, w as sourceTiers, x as sourceTiersResearch, y as sourcesSchema, z as toolSlugs, D as toolsChapterSchema } from './schemas-CKipJ5Ie.js';
6
6
  export { KIND_LABEL, ResolvedTheoremLabel, THEOREM_KINDS, TheoremKind, TheoremLabelProps, resolveTheoremNumber, theoremLabel } from './lib/theorem-label.js';
7
- export { D as DomainScore, E as ExamBlueprint, a as ExamQuestion, b as ExamResult, R as RoutingChapter, c as buildExamManifest, d as deriveDomainRouting, s as sampleExam, e as scoreExam, f as shuffle, g as spreadBlueprint } from './exam-manifest-DbTHo90M.js';
8
- export { F as FlashcardRef, b as buildFlashcardDeck } from './flashcards-DPFFQhP8.js';
7
+ export { D as DomainScore, E as ExamBlueprint, a as ExamQuestion, b as ExamResult, R as RoutingChapter, c as buildExamManifest, d as deriveDomainRouting, s as sampleExam, e as scoreExam, f as shuffle, g as spreadBlueprint } from './exam-manifest-DnqyzZ9I.js';
8
+ export { F as FlashcardRef, b as buildFlashcardDeck } from './flashcards-okekZcl8.js';
9
9
  import 'astro/zod';
10
10
 
11
11
  /**
@@ -360,6 +360,69 @@ declare function distinctChaptersSorted<T extends {
360
360
  data: Pick<Question, 'chapter'>;
361
361
  }>(entries: readonly T[]): string[];
362
362
 
363
+ /**
364
+ * section-map.ts — PURE heading-selection + scrollspy logic for the right-gutter
365
+ * "On this page" section map (#section-map).
366
+ *
367
+ * No DOM, no Preact: this is the testable core under both ChapterTOC.astro (the
368
+ * collapsed mobile fallback) and the SectionMap island (the sticky gutter nav).
369
+ * Two pure, total functions:
370
+ *
371
+ * - tocHeadings: the ONE filter (h2+h3) that both the fallback TOC and the
372
+ * gutter map share — a single source of truth so the two
373
+ * never disagree about which headings are "on this page".
374
+ * - pickActive: given the headings currently intersecting the viewport (slug
375
+ * + viewport-relative top), choose which one is "active". The
376
+ * island feeds it IntersectionObserver state; node:test feeds
377
+ * it plain arrays. Browser geometry stays OUT of this file.
378
+ *
379
+ * Both are unit-tested in tests/section-map.test.mjs (node:test, no browser),
380
+ * mirroring exam-engine.ts.
381
+ */
382
+
383
+ /**
384
+ * Filter a chapter's headings to the TOC set: depth 2–3 only. h1 is the chapter
385
+ * title (rendered by ChapterHeader, not the body) and h4+ is noise in an anchor
386
+ * list. This is the shared contract — ChapterTOC.astro and SectionMap.astro both
387
+ * call it so the fallback and the gutter map carry IDENTICAL entries. Pure: it
388
+ * copies (filter) rather than mutating the input.
389
+ */
390
+ declare function tocHeadings(headings: MarkdownHeading[]): MarkdownHeading[];
391
+ /**
392
+ * One visible heading, as the island measures it: its slug and its
393
+ * viewport-relative top (CSS pixels — `getBoundingClientRect().top`). A negative
394
+ * `top` means the heading has scrolled above the top of the viewport.
395
+ */
396
+ interface VisibleHeading {
397
+ slug: string;
398
+ /** Viewport-relative top in px (negative = scrolled above the fold). */
399
+ top: number;
400
+ }
401
+ /**
402
+ * Choose the active section slug from the headings currently intersecting the
403
+ * viewport.
404
+ *
405
+ * Rule (a stable, total scrollspy):
406
+ * 1. Prefer the topmost heading at or below the top of the viewport — the
407
+ * visible heading with the SMALLEST non-negative `top`. That's the section
408
+ * the reader has just scrolled to / is reading into.
409
+ * 2. If every visible heading is above the fold (all `top` negative — e.g. a
410
+ * heading STRADDLING the top edge: its top is slightly negative but its box
411
+ * still overlaps the top zone, so it's intersecting yet has no non-negative
412
+ * sibling below it), fall back to the one nearest the fold from above: the
413
+ * GREATEST `top` (closest to 0 from the negative side). That keeps the
414
+ * enclosing section lit instead of going dark mid-section. (A heading whose
415
+ * box FULLY scrolled above the top has left `inView` entirely — it isn't in
416
+ * `visible` at all, and is handled by the empty-set → `prev` branch below.)
417
+ * 3. If NOTHING is visible (the observer reports an empty set — between two
418
+ * sparse intersections, or scrolled past the last heading), retain `prev`
419
+ * so the highlight is sticky rather than flickering off.
420
+ *
421
+ * Pure + total: no DOM, no throw, deterministic. Ties (equal `top`) resolve to
422
+ * the first in iteration order, which the island passes in document order.
423
+ */
424
+ declare function pickActive(visible: ReadonlyArray<VisibleHeading>, prev: string | null): string | null;
425
+
363
426
  /** One exercise as emitted by `book-scaffold build-exercises` (src/data/exercises.json). */
364
427
  interface ReviewExercise {
365
428
  id: string;
@@ -488,4 +551,4 @@ type TipsConfigInput = Omit<TipsConfig, typeof TipsConfigBrand | '__tipsConfigVe
488
551
  */
489
552
  declare function defineTips(opts: TipsConfigInput): TipsConfig;
490
553
 
491
- export { ACADEMIC_PART_NAMES, BRANDON_PORTFOLIO_DEFAULT, BUILTIN_STYLES, BookConfigOptions, BookScaffoldIntegrationOptions, ChaptersRenderer, DEFAULT_GITHUB_BRANCH, type Freshness, type FreshnessStatus, type PartReviewGroup, type PartReviewSelection, Question, type ReviewChapter, type ReviewExercise, Style, type TipsConfig, type TipsConfigInput, UNKNOWN_PART_ORDINAL, type VolatilityLevel, academicChaptersRenderer, academicPartHeading, academicPartName, academicPartOrdinal, academicParts, academicStyle, assertEnumProp, assertKnownDomain, bookScaffoldIntegration, buildGithubUrl, chapterLabel, chapterSortKey, courseNotesStyle, defineBookConfig, defineMdxComponents, defineTips, deriveObjectiveMap, distinctChaptersSorted, fallbackChaptersRenderer, freshnessLabel, getFreshness, groupByChapter, groupByDomain, minimalStyle, originUrlFromGitConfig, parseRepoSlug, researchPortfolioStyle, resolveBookHref, resolveGithubRepo, selectPartExercises, sortQuestions, toolsChaptersRenderer, toolsStyle, volatilityLevels };
554
+ export { ACADEMIC_PART_NAMES, BRANDON_PORTFOLIO_DEFAULT, BUILTIN_STYLES, BookConfigOptions, BookScaffoldIntegrationOptions, ChaptersRenderer, DEFAULT_GITHUB_BRANCH, type Freshness, type FreshnessStatus, type PartReviewGroup, type PartReviewSelection, Question, type ReviewChapter, type ReviewExercise, Style, type TipsConfig, type TipsConfigInput, UNKNOWN_PART_ORDINAL, type VisibleHeading, type VolatilityLevel, academicChaptersRenderer, academicPartHeading, academicPartName, academicPartOrdinal, academicParts, academicStyle, assertEnumProp, assertKnownDomain, bookScaffoldIntegration, buildGithubUrl, chapterLabel, chapterSortKey, courseNotesStyle, defineBookConfig, defineMdxComponents, defineTips, deriveObjectiveMap, distinctChaptersSorted, fallbackChaptersRenderer, freshnessLabel, getFreshness, groupByChapter, groupByDomain, minimalStyle, originUrlFromGitConfig, parseRepoSlug, pickActive, researchPortfolioStyle, resolveBookHref, resolveGithubRepo, selectPartExercises, sortQuestions, tocHeadings, toolsChaptersRenderer, toolsStyle, volatilityLevels };
package/dist/index.mjs CHANGED
@@ -168,6 +168,7 @@ var chapterStatus = [
168
168
  "scaffolded",
169
169
  "planned"
170
170
  ];
171
+ var layoutModes = ["default", "wide"];
171
172
  var citationBackstops = ["research-kb", "manual", "unverified"];
172
173
  var provenanceObject = z.object({
173
174
  ai_tools: z.array(z.string()).default([]),
@@ -186,6 +187,8 @@ var academicChapterSchema = z.object({
186
187
  title: z.string().min(1),
187
188
  slug: z.string().optional(),
188
189
  // v4.9.0: explicit URL slug override (else filename → entry.id)
190
+ layout: z.enum(layoutModes).optional(),
191
+ // 1d: per-page width knob (default measure | 'wide'); threaded as data-layout
189
192
  status: z.enum(chapterStatus),
190
193
  roadmap_lines: z.tuple([z.number().int(), z.number().int()]).optional(),
191
194
  code_path: z.string().optional(),
@@ -207,6 +210,8 @@ var toolsChapterSchema = z.object({
207
210
  title: z.string().min(1),
208
211
  slug: z.string().optional(),
209
212
  // v4.9.0: explicit URL slug override (else filename → entry.id)
213
+ layout: z.enum(layoutModes).optional(),
214
+ // 1d: per-page width knob (default measure | 'wide'); threaded as data-layout
210
215
  part: z.number().int().min(0).max(10),
211
216
  chapter: z.number().int().min(0).max(99),
212
217
  volatility: z.enum(volatilityLevels),
@@ -232,6 +237,8 @@ var courseNotesChapterSchema = z.object({
232
237
  title: z.string().min(1),
233
238
  slug: z.string().optional(),
234
239
  // v4.9.0: explicit URL slug override (else filename → entry.id)
240
+ layout: z.enum(layoutModes).optional(),
241
+ // 1d: per-page width knob (default measure | 'wide'); threaded as data-layout
235
242
  chapter: z.number().int().min(0).max(99),
236
243
  part: z.number().int().min(0).max(20).default(1),
237
244
  description: z.string().optional(),
@@ -268,6 +275,8 @@ var researchPortfolioChapterSchema = z.object({
268
275
  title: z.string().min(1),
269
276
  slug: z.string().optional(),
270
277
  // explicit slug override (otherwise filename)
278
+ layout: z.enum(layoutModes).optional(),
279
+ // 1d: per-page width knob (default measure | 'wide'); threaded as data-layout
271
280
  description: z.string().optional(),
272
281
  // Hierarchy — accept either academic-style or tools-style; all optional.
273
282
  // The academic 'part' field is a string enum; tools 'part' is a number.
@@ -576,7 +585,7 @@ var academicProfile = defineProfile({
576
585
  landing: true
577
586
  // v4.5.0: auto-inject minimal root landing; consumers override via src/pages/index.astro
578
587
  },
579
- styles: ["tokens.css", "layout.css", "callouts.css", "chapter.css", "typography.css", "print.css"],
588
+ styles: ["tokens.css", "layout.css", "callouts.css", "chapter.css", "typography.css", "print.css", "section-map.css"],
580
589
  katex: true,
581
590
  chaptersRenderer: academicChaptersRenderer,
582
591
  // v3.7.0 (#35) — owns /chapters semantics if consumer opts in via routes.chapters
@@ -715,7 +724,8 @@ var toolsProfile = defineProfile({
715
724
  "typography.css",
716
725
  "print.css",
717
726
  "convergence.css",
718
- "tool-filter.css"
727
+ "tool-filter.css",
728
+ "section-map.css"
719
729
  ],
720
730
  chaptersRenderer: toolsChaptersRenderer
721
731
  // v3.7.0 (#35) — owns /chapters semantics for tools shape
@@ -800,7 +810,7 @@ var minimalProfile = defineProfile({
800
810
  landing: true
801
811
  // v4.5.0: auto-inject minimal root landing
802
812
  },
803
- styles: ["tokens.css", "layout.css", "callouts.css", "chapter.css", "typography.css", "print.css"],
813
+ styles: ["tokens.css", "layout.css", "callouts.css", "chapter.css", "typography.css", "print.css", "section-map.css"],
804
814
  // v3.7.0 (#35): minimal aliases tools schema; fallback renderer field-dispatches if a consumer opts into routes.chapters
805
815
  chaptersRenderer: fallbackChaptersRenderer
806
816
  });
@@ -833,7 +843,7 @@ var courseNotesProfile = defineProfile({
833
843
  landing: true
834
844
  // v4.5.0: auto-inject minimal root landing
835
845
  },
836
- styles: ["tokens.css", "layout.css", "callouts.css", "chapter.css", "typography.css", "print.css"],
846
+ styles: ["tokens.css", "layout.css", "callouts.css", "chapter.css", "typography.css", "print.css", "section-map.css"],
837
847
  // v3.7.0 (#35): course-notes schema has tools-style fields (chapter, volatility, sources) — fallback renderer dispatches via tools renderer
838
848
  chaptersRenderer: fallbackChaptersRenderer,
839
849
  // v4.6.0 (#76 Secondary): exclude /print/ from sitemap — print-friendly
@@ -870,7 +880,7 @@ var researchPortfolioProfile = defineProfile({
870
880
  landing: true
871
881
  // v4.5.0: auto-inject minimal root landing
872
882
  },
873
- styles: ["tokens.css", "layout.css", "callouts.css", "chapter.css", "typography.css", "print.css"],
883
+ styles: ["tokens.css", "layout.css", "callouts.css", "chapter.css", "typography.css", "print.css", "section-map.css"],
874
884
  katex: true,
875
885
  // math is common in research content
876
886
  // v3.7.0 (#35): portfolio schema is a union of academic + tools shapes — fallback renderer dispatches per chapter via field presence
@@ -1749,6 +1759,26 @@ function buildFlashcardDeck(entries) {
1749
1759
  return entries.filter((e) => !e.data.draft).map((e) => ({ id: e.id, front: e.data.term })).sort((a, b) => a.front.localeCompare(b.front));
1750
1760
  }
1751
1761
 
1762
+ // src/lib/section-map.ts
1763
+ function tocHeadings(headings) {
1764
+ return headings.filter((h) => h.depth >= 2 && h.depth <= 3);
1765
+ }
1766
+ function pickActive(visible, prev) {
1767
+ if (visible.length === 0) return prev;
1768
+ let bestNonNeg = null;
1769
+ let bestAbove = null;
1770
+ for (const h of visible) {
1771
+ if (h.top >= 0) {
1772
+ if (bestNonNeg === null || h.top < bestNonNeg.top) bestNonNeg = h;
1773
+ } else {
1774
+ if (bestAbove === null || h.top > bestAbove.top) bestAbove = h;
1775
+ }
1776
+ }
1777
+ if (bestNonNeg !== null) return bestNonNeg.slug;
1778
+ if (bestAbove !== null) return bestAbove.slug;
1779
+ return prev;
1780
+ }
1781
+
1752
1782
  // src/lib/part-review.ts
1753
1783
  function selectPartExercises(chapters, byChapter, part) {
1754
1784
  const want = String(part);
@@ -1844,6 +1874,7 @@ export {
1844
1874
  glossarySchema,
1845
1875
  groupByChapter,
1846
1876
  groupByDomain,
1877
+ layoutModes,
1847
1878
  minimalChapterSchema,
1848
1879
  minimalStyle,
1849
1880
  normalizeFrontmatterConfig,
@@ -1851,6 +1882,7 @@ export {
1851
1882
  parseRepoSlug,
1852
1883
  patternCategories,
1853
1884
  patternsSchema,
1885
+ pickActive,
1854
1886
  provenanceObject,
1855
1887
  provenanceSchema,
1856
1888
  questionDifficulties,
@@ -1875,6 +1907,7 @@ export {
1875
1907
  sourcesSchema,
1876
1908
  spreadBlueprint,
1877
1909
  theoremLabel,
1910
+ tocHeadings,
1878
1911
  toolSlugs,
1879
1912
  toolsChapterSchema,
1880
1913
  toolsChaptersRenderer,
@@ -25,6 +25,7 @@ declare const changeKinds: readonly ["added", "removed", "changed", "deprecated"
25
25
  declare const patternCategories: readonly ["safety", "scale", "context", "interaction", "extension", "other"];
26
26
  declare const academicParts: readonly ["foundations", "ssm-core", "beyond-ssm", "integration", "synthesis"];
27
27
  declare const chapterStatus: readonly ["implemented", "chapter_only", "reading_only", "prose_only", "code_only", "scaffolded", "planned"];
28
+ declare const layoutModes: readonly ["default", "wide"];
28
29
  declare const citationBackstops: readonly ["research-kb", "manual", "unverified"];
29
30
  declare const provenanceObject: z.ZodObject<{
30
31
  ai_tools: z.ZodDefault<z.ZodArray<z.ZodString>>;
@@ -67,6 +68,10 @@ declare const academicChapterSchema: z.ZodObject<{
67
68
  }>;
68
69
  title: z.ZodString;
69
70
  slug: z.ZodOptional<z.ZodString>;
71
+ layout: z.ZodOptional<z.ZodEnum<{
72
+ default: "default";
73
+ wide: "wide";
74
+ }>>;
70
75
  status: z.ZodEnum<{
71
76
  implemented: "implemented";
72
77
  chapter_only: "chapter_only";
@@ -106,6 +111,10 @@ declare const academicChapterSchema: z.ZodObject<{
106
111
  declare const toolsChapterSchema: z.ZodObject<{
107
112
  title: z.ZodString;
108
113
  slug: z.ZodOptional<z.ZodString>;
114
+ layout: z.ZodOptional<z.ZodEnum<{
115
+ default: "default";
116
+ wide: "wide";
117
+ }>>;
109
118
  part: z.ZodNumber;
110
119
  chapter: z.ZodNumber;
111
120
  volatility: z.ZodEnum<{
@@ -148,6 +157,10 @@ declare const toolsChapterSchema: z.ZodObject<{
148
157
  declare const minimalChapterSchema: z.ZodObject<{
149
158
  title: z.ZodString;
150
159
  slug: z.ZodOptional<z.ZodString>;
160
+ layout: z.ZodOptional<z.ZodEnum<{
161
+ default: "default";
162
+ wide: "wide";
163
+ }>>;
151
164
  part: z.ZodNumber;
152
165
  chapter: z.ZodNumber;
153
166
  volatility: z.ZodEnum<{
@@ -205,6 +218,10 @@ declare const sourceTiersResearch: readonly ["T1", "T2", "T3", "T4"];
205
218
  declare const courseNotesChapterSchema: z.ZodObject<{
206
219
  title: z.ZodString;
207
220
  slug: z.ZodOptional<z.ZodString>;
221
+ layout: z.ZodOptional<z.ZodEnum<{
222
+ default: "default";
223
+ wide: "wide";
224
+ }>>;
208
225
  chapter: z.ZodNumber;
209
226
  part: z.ZodDefault<z.ZodNumber>;
210
227
  description: z.ZodOptional<z.ZodString>;
@@ -269,6 +286,10 @@ declare const courseNotesChapterSchema: z.ZodObject<{
269
286
  declare const researchPortfolioChapterSchema: z.ZodObject<{
270
287
  title: z.ZodString;
271
288
  slug: z.ZodOptional<z.ZodString>;
289
+ layout: z.ZodOptional<z.ZodEnum<{
290
+ default: "default";
291
+ wide: "wide";
292
+ }>>;
272
293
  description: z.ZodOptional<z.ZodString>;
273
294
  part: z.ZodOptional<z.ZodUnion<readonly [z.ZodNumber, z.ZodString]>>;
274
295
  week: z.ZodOptional<z.ZodNumber>;
@@ -488,4 +509,4 @@ declare const glossarySchema: z.ZodObject<{
488
509
  }, z.core.$strict>;
489
510
  type GlossaryTerm = z.infer<typeof glossarySchema>;
490
511
 
491
- export { type AcademicChapter as A, type BloomLevel as B, type CourseNotesChapter as C, volatilityLevels as D, type GlossaryTerm as G, type MinimalChapter as M, type Provenance as P, type Question as Q, type ResearchPortfolioChapter as R, type ToolsChapter as T, type QuestionType as a, academicChapterSchema as b, academicParts as c, bloomLevels as d, changeKinds as e, changelogSchema as f, chapterStatus as g, citationBackstops as h, courseNotesChapterSchema as i, glossarySchema as j, patternsSchema as k, provenanceObject as l, minimalChapterSchema as m, provenanceSchema as n, questionSchema as o, patternCategories as p, questionDifficulties as q, questionTypes as r, refineQuestion as s, refinedQuestionSchema as t, researchPortfolioChapterSchema as u, sourceTiers as v, sourceTiersResearch as w, sourcesSchema as x, toolSlugs as y, toolsChapterSchema as z };
512
+ export { type AcademicChapter as A, type BloomLevel as B, type CourseNotesChapter as C, toolsChapterSchema as D, volatilityLevels as E, type GlossaryTerm as G, type MinimalChapter as M, type Provenance as P, type Question as Q, type ResearchPortfolioChapter as R, type ToolsChapter as T, type QuestionType as a, academicChapterSchema as b, academicParts as c, bloomLevels as d, changeKinds as e, changelogSchema as f, chapterStatus as g, citationBackstops as h, courseNotesChapterSchema as i, glossarySchema as j, patternsSchema as k, layoutModes as l, minimalChapterSchema as m, provenanceObject as n, provenanceSchema as o, patternCategories as p, questionDifficulties as q, questionSchema as r, questionTypes as s, refineQuestion as t, refinedQuestionSchema as u, researchPortfolioChapterSchema as v, sourceTiers as w, sourceTiersResearch as x, sourcesSchema as y, toolSlugs as z };
package/dist/schemas.d.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import { defineCollection } from 'astro:content';
2
- import { g as BookSchemasOptions } from './types-CGN95mrX.js';
2
+ import { g as BookSchemasOptions } from './types-Bn7rKhgP.js';
3
3
  import 'astro';
4
- import './schemas-DDWDRUxs.js';
4
+ import './schemas-CKipJ5Ie.js';
5
5
  import 'astro/zod';
6
6
 
7
7
  /**
package/dist/schemas.mjs CHANGED
@@ -52,6 +52,7 @@ var chapterStatus = [
52
52
  "scaffolded",
53
53
  "planned"
54
54
  ];
55
+ var layoutModes = ["default", "wide"];
55
56
  var citationBackstops = ["research-kb", "manual", "unverified"];
56
57
  var provenanceObject = z.object({
57
58
  ai_tools: z.array(z.string()).default([]),
@@ -70,6 +71,8 @@ var academicChapterSchema = z.object({
70
71
  title: z.string().min(1),
71
72
  slug: z.string().optional(),
72
73
  // v4.9.0: explicit URL slug override (else filename → entry.id)
74
+ layout: z.enum(layoutModes).optional(),
75
+ // 1d: per-page width knob (default measure | 'wide'); threaded as data-layout
73
76
  status: z.enum(chapterStatus),
74
77
  roadmap_lines: z.tuple([z.number().int(), z.number().int()]).optional(),
75
78
  code_path: z.string().optional(),
@@ -91,6 +94,8 @@ var toolsChapterSchema = z.object({
91
94
  title: z.string().min(1),
92
95
  slug: z.string().optional(),
93
96
  // v4.9.0: explicit URL slug override (else filename → entry.id)
97
+ layout: z.enum(layoutModes).optional(),
98
+ // 1d: per-page width knob (default measure | 'wide'); threaded as data-layout
94
99
  part: z.number().int().min(0).max(10),
95
100
  chapter: z.number().int().min(0).max(99),
96
101
  volatility: z.enum(volatilityLevels),
@@ -116,6 +121,8 @@ var courseNotesChapterSchema = z.object({
116
121
  title: z.string().min(1),
117
122
  slug: z.string().optional(),
118
123
  // v4.9.0: explicit URL slug override (else filename → entry.id)
124
+ layout: z.enum(layoutModes).optional(),
125
+ // 1d: per-page width knob (default measure | 'wide'); threaded as data-layout
119
126
  chapter: z.number().int().min(0).max(99),
120
127
  part: z.number().int().min(0).max(20).default(1),
121
128
  description: z.string().optional(),
@@ -152,6 +159,8 @@ var researchPortfolioChapterSchema = z.object({
152
159
  title: z.string().min(1),
153
160
  slug: z.string().optional(),
154
161
  // explicit slug override (otherwise filename)
162
+ layout: z.enum(layoutModes).optional(),
163
+ // 1d: per-page width knob (default measure | 'wide'); threaded as data-layout
155
164
  description: z.string().optional(),
156
165
  // Hierarchy — accept either academic-style or tools-style; all optional.
157
166
  // The academic 'part' field is a string enum; tools 'part' is a number.
@@ -454,7 +463,7 @@ var academicProfile = defineProfile({
454
463
  landing: true
455
464
  // v4.5.0: auto-inject minimal root landing; consumers override via src/pages/index.astro
456
465
  },
457
- styles: ["tokens.css", "layout.css", "callouts.css", "chapter.css", "typography.css", "print.css"],
466
+ styles: ["tokens.css", "layout.css", "callouts.css", "chapter.css", "typography.css", "print.css", "section-map.css"],
458
467
  katex: true,
459
468
  chaptersRenderer: academicChaptersRenderer,
460
469
  // v3.7.0 (#35) — owns /chapters semantics if consumer opts in via routes.chapters
@@ -593,7 +602,8 @@ var toolsProfile = defineProfile({
593
602
  "typography.css",
594
603
  "print.css",
595
604
  "convergence.css",
596
- "tool-filter.css"
605
+ "tool-filter.css",
606
+ "section-map.css"
597
607
  ],
598
608
  chaptersRenderer: toolsChaptersRenderer
599
609
  // v3.7.0 (#35) — owns /chapters semantics for tools shape
@@ -678,7 +688,7 @@ var minimalProfile = defineProfile({
678
688
  landing: true
679
689
  // v4.5.0: auto-inject minimal root landing
680
690
  },
681
- styles: ["tokens.css", "layout.css", "callouts.css", "chapter.css", "typography.css", "print.css"],
691
+ styles: ["tokens.css", "layout.css", "callouts.css", "chapter.css", "typography.css", "print.css", "section-map.css"],
682
692
  // v3.7.0 (#35): minimal aliases tools schema; fallback renderer field-dispatches if a consumer opts into routes.chapters
683
693
  chaptersRenderer: fallbackChaptersRenderer
684
694
  });
@@ -711,7 +721,7 @@ var courseNotesProfile = defineProfile({
711
721
  landing: true
712
722
  // v4.5.0: auto-inject minimal root landing
713
723
  },
714
- styles: ["tokens.css", "layout.css", "callouts.css", "chapter.css", "typography.css", "print.css"],
724
+ styles: ["tokens.css", "layout.css", "callouts.css", "chapter.css", "typography.css", "print.css", "section-map.css"],
715
725
  // v3.7.0 (#35): course-notes schema has tools-style fields (chapter, volatility, sources) — fallback renderer dispatches via tools renderer
716
726
  chaptersRenderer: fallbackChaptersRenderer,
717
727
  // v4.6.0 (#76 Secondary): exclude /print/ from sitemap — print-friendly
@@ -748,7 +758,7 @@ var researchPortfolioProfile = defineProfile({
748
758
  landing: true
749
759
  // v4.5.0: auto-inject minimal root landing
750
760
  },
751
- styles: ["tokens.css", "layout.css", "callouts.css", "chapter.css", "typography.css", "print.css"],
761
+ styles: ["tokens.css", "layout.css", "callouts.css", "chapter.css", "typography.css", "print.css", "section-map.css"],
752
762
  katex: true,
753
763
  // math is common in research content
754
764
  // v3.7.0 (#35): portfolio schema is a union of academic + tools shapes — fallback renderer dispatches per chapter via field presence
@@ -1,5 +1,5 @@
1
1
  import { AstroIntegration, AstroUserConfig } from 'astro';
2
- import { A as AcademicChapter, T as ToolsChapter, M as MinimalChapter, C as CourseNotesChapter, R as ResearchPortfolioChapter } from './schemas-DDWDRUxs.js';
2
+ import { A as AcademicChapter, T as ToolsChapter, M as MinimalChapter, C as CourseNotesChapter, R as ResearchPortfolioChapter } from './schemas-CKipJ5Ie.js';
3
3
 
4
4
  /**
5
5
  * src/lib/chapters-renderer.ts — per-profile strategy interface for the
@@ -25,6 +25,7 @@ import Base from './Base.astro';
25
25
  import bookConfig from 'virtual:book-scaffold/book-config';
26
26
  import ChapterHeader from '../components/ChapterHeader.astro';
27
27
  import ChapterTOC from '../components/ChapterTOC.astro';
28
+ import SectionMap from '../components/SectionMap.astro';
28
29
  import ChapterNav from '../components/ChapterNav.astro';
29
30
 
30
31
  interface Props {
@@ -44,6 +45,39 @@ const articlePublished = (entry.data as { published?: Date }).published;
44
45
  const articleUpdated = (entry.data as { updated?: Date }).updated;
45
46
  const articleTags = ((entry.data as { tags?: string[] }).tags ?? []) as string[];
46
47
  const chapterImage = (entry.data as { image?: string }).image;
48
+
49
+ // (#section-map): best-effort context label for the gutter section map, e.g.
50
+ // "Part 2 · Ch 4" or "Week 1". All hierarchy fields are profile-dependent and
51
+ // Zod-optional (academic carries part+week; tools/research-portfolio carry
52
+ // part+chapter; minimal carries none), so each piece is included only when
53
+ // present — the label is omitted entirely (undefined) when nothing is derivable.
54
+ const dataAny = entry.data as {
55
+ part?: string | number;
56
+ chapter?: number;
57
+ week?: number;
58
+ layout?: 'default' | 'wide';
59
+ };
60
+
61
+ // 1d: per-page width knob. `layout` is Zod-optional on every profile schema
62
+ // (closed enum 'default'|'wide'). Emit `data-layout` ONLY for a non-default
63
+ // value so existing chapters (no `layout`, or `layout: default`) render the
64
+ // attribute-free <article> byte-identically — `undefined` makes Astro drop the
65
+ // attribute, and only `.prose[data-layout="wide"]` carries a rule in layout.css.
66
+ const layoutMode =
67
+ dataAny.layout && dataAny.layout !== 'default' ? dataAny.layout : undefined;
68
+ const labelParts: string[] = [];
69
+ if (dataAny.part !== undefined && dataAny.part !== '') {
70
+ // Numeric part → "Part N"; string-enum part (academic, e.g. "foundations")
71
+ // → title-case it as-is.
72
+ labelParts.push(
73
+ typeof dataAny.part === 'number'
74
+ ? `Part ${dataAny.part}`
75
+ : dataAny.part.charAt(0).toUpperCase() + dataAny.part.slice(1),
76
+ );
77
+ }
78
+ if (dataAny.chapter !== undefined) labelParts.push(`Ch ${dataAny.chapter}`);
79
+ else if (dataAny.week !== undefined) labelParts.push(`Week ${dataAny.week}`);
80
+ const sectionMapLabel = labelParts.length > 0 ? labelParts.join(' · ') : undefined;
47
81
  ---
48
82
 
49
83
  <Base
@@ -62,9 +96,10 @@ const chapterImage = (entry.data as { image?: string }).image;
62
96
  )}
63
97
  {articleTags.map((t) => <meta property="article:tag" content={t} />)}
64
98
  </Fragment>
65
- <article class="prose">
99
+ <article class="prose" data-layout={layoutMode}>
66
100
  <ChapterHeader data={entry.data} />
67
101
  <ChapterTOC headings={headings} />
102
+ <SectionMap headings={headings} label={sectionMapLabel} />
68
103
  <slot />
69
104
  <ChapterNav currentId={entry.id} />
70
105
  </article>
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@brandon_m_behring/book-scaffold-astro",
3
3
  "description": "Astro 6 + MDX toolkit for long-form technical books. Profile-aware (academic / tools / minimal); ships Tufte typography, KaTeX, BibTeX citations, Pagefind, Cloudflare Workers deploy. See PACKAGE_DESIGN.md for the API contract.",
4
- "version": "4.24.0",
4
+ "version": "4.25.0",
5
5
  "type": "module",
6
6
  "license": "MIT",
7
7
  "author": "Brandon Behring",
@@ -59,13 +59,17 @@
59
59
  "./components/Diagnostic.astro": "./components/Diagnostic.astro",
60
60
  "./components/Divergence.astro": "./components/Divergence.astro",
61
61
  "./components/DynConnect.astro": "./components/DynConnect.astro",
62
+ "./components/Epigraph.astro": "./components/Epigraph.astro",
63
+ "./components/EvidenceTag.astro": "./components/EvidenceTag.astro",
62
64
  "./components/ExampleBox.astro": "./components/ExampleBox.astro",
63
65
  "./components/Exercise.astro": "./components/Exercise.astro",
64
66
  "./components/ExerciseSolutions.astro": "./components/ExerciseSolutions.astro",
65
67
  "./components/Figure.astro": "./components/Figure.astro",
66
68
  "./components/InsightBox.astro": "./components/InsightBox.astro",
67
69
  "./components/KeyIdea.astro": "./components/KeyIdea.astro",
70
+ "./components/MarginFigure.astro": "./components/MarginFigure.astro",
68
71
  "./components/MarginNote.astro": "./components/MarginNote.astro",
72
+ "./components/Newthought.astro": "./components/Newthought.astro",
69
73
  "./components/NoteBox.astro": "./components/NoteBox.astro",
70
74
  "./components/ObjectiveMap.astro": "./components/ObjectiveMap.astro",
71
75
  "./components/OpenQuestion.astro": "./components/OpenQuestion.astro",
@@ -82,6 +86,7 @@
82
86
  "./components/Rationale.astro": "./components/Rationale.astro",
83
87
  "./components/Recovery.astro": "./components/Recovery.astro",
84
88
  "./components/ResultBox.astro": "./components/ResultBox.astro",
89
+ "./components/SectionMap.astro": "./components/SectionMap.astro",
85
90
  "./components/Sidebar.astro": "./components/Sidebar.astro",
86
91
  "./components/Sidenote.astro": "./components/Sidenote.astro",
87
92
  "./components/SkillBox.astro": "./components/SkillBox.astro",
@@ -111,6 +116,10 @@
111
116
  "types": "./dist/components/Flashcards.d.ts",
112
117
  "import": "./dist/components/Flashcards.mjs"
113
118
  },
119
+ "./components/SectionMap": {
120
+ "types": "./dist/components/SectionMap.d.ts",
121
+ "import": "./dist/components/SectionMap.mjs"
122
+ },
114
123
  "./components/WarnBox.astro": "./components/WarnBox.astro",
115
124
  "./components/WeekRef.astro": "./components/WeekRef.astro",
116
125
  "./components/WorkedExample.astro": "./components/WorkedExample.astro",
@@ -127,6 +136,7 @@
127
136
  "./styles/tool-filter.css": "./styles/tool-filter.css",
128
137
  "./styles/exam-runner.css": "./styles/exam-runner.css",
129
138
  "./styles/flashcards.css": "./styles/flashcards.css",
139
+ "./styles/section-map.css": "./styles/section-map.css",
130
140
  "./layouts/Base.astro": "./layouts/Base.astro",
131
141
  "./layouts/Chapter.astro": "./layouts/Chapter.astro",
132
142
  "./lib": {
@@ -65,8 +65,12 @@ Supported `type` values: `theorem`, `proposition`, `lemma`, `corollary`, `defini
65
65
  | `Cite` | Inline citation linked to `/references` | `<Cite key="gu2024mamba" page="3" />` |
66
66
  | `XRef` | Cross-reference to a labeled element | `<XRef id="thm:zoh-stability" />` |
67
67
  | `Figure` | Image/SVG + caption + id; local SVGs inline for a11y + dark mode (`alt`, `desc`) | `<Figure src="/figures/week04/eigenvalues.svg" caption="…" alt="…" id="fig-eig" />` |
68
- | `MarginNote` | Right-margin annotation (Tufte-style) | `<MarginNote>side comment</MarginNote>` |
69
- | `Sidenote` | Auto-numbered marginalia | `<Sidenote>numbered note</Sidenote>` |
68
+ | `MarginFigure` | A `Figure` that **floats into the right gutter** (Tufte margin figure; ≥64rem, inline on mobile). Same props as `Figure`; rendering delegated to it. For full-bleed use `<Figure class="wide">` | `<MarginFigure src="/figures/week04/eig.svg" caption="…" alt="…" id="fig-eig-m" />` |
69
+ | `MarginNote` | **Inline** colored callout in the text column (does **not** float to the margin, despite the name) — load-bearing aside the reader must see | `<MarginNote>side comment</MarginNote>` |
70
+ | `Sidenote` | Auto-numbered Tufte marginalia that **floats into the right gutter** on desktop (inline on mobile) | `<Sidenote>numbered note</Sidenote>` |
71
+ | `EvidenceTag` | Inline claim-confidence pill after a claim (`verified`/`inference`/`audit-corrected`) | `<EvidenceTag kind="verified" /> … <EvidenceTag kind="inference" />` |
72
+ | `Newthought` | Tufte run-in small-caps section opener | `<p><Newthought>In practice</Newthought>, …</p>` |
73
+ | `Epigraph` | Chapter-opening italic quotation with right-aligned attribution | `<Epigraph>…<Fragment slot="attribution">Hamming</Fragment></Epigraph>` |
70
74
  | `WeekRef` | Jump-link to a week chapter | `<WeekRef week={4} />` |
71
75
  | `CodeRef` | GitHub-deep-link to file:line | `<CodeRef path="experiments/jax/foo.py" line={42} />` |
72
76
  | `CodeBlock` | Embed code-file range with syntax highlight | `<CodeBlock src="…" lines="10-30" />` |
@@ -95,6 +95,45 @@ Common failures:
95
95
 
96
96
  If you hit friction with the TikZ pipeline (a TikZ feature that doesn't compile, an obscure error message, a workflow pattern that doesn't fit), file an issue at https://github.com/brandon-behring/book-scaffold-astro/issues with the `consumer:<your-workspace>` label. v4.x is the iteration window.
97
97
 
98
+ ## Margin & full-width figure placement (1d)
99
+
100
+ Once a figure exists, three placements are available — all reuse the existing Tufte float + gutter technique (the same one `Sidenote`/SectionMap use); none introduce a grid.
101
+
102
+ **In the running text (default).** Plain `<Figure>` constrained to the main measure:
103
+
104
+ ```mdx
105
+ <Figure src="/figures/topic/diagram.svg" caption="…" alt="…" id="fig-x" />
106
+ ```
107
+
108
+ **In the right margin** — `<MarginFigure>` floats into the gutter at ≥64rem and reflows inline below on mobile. Same props as `<Figure>` (rendering is delegated to it); `width` defaults to `100%` of the ~28ch gutter column, and the caption shrinks:
109
+
110
+ ```mdx
111
+ import MarginFigure from '@brandon_m_behring/book-scaffold-astro/components/MarginFigure.astro';
112
+
113
+ <MarginFigure src="/figures/topic/diagram.svg" caption="A small aside figure." alt="…" id="fig-x-margin" />
114
+ ```
115
+
116
+ **Full-bleed** — a wide figure/table/code block that spans the gutter column too. Use the canonical `.wide` escape (or its alias `.column-page`):
117
+
118
+ ```mdx
119
+ <Figure src="/figures/topic/wide-plot.svg" caption="…" alt="…" id="fig-wide" class="wide" />
120
+ ```
121
+
122
+ **Generic gutter block** — to float a non-figure block (a small note, a key figure number) into the margin, add `class="column-margin"` to any element. It uses the identical gutter float.
123
+
124
+ ### Per-page width knob: `layout: wide`
125
+
126
+ For a chapter that's figure- or table-heavy, widen the **main text measure** via an optional frontmatter field (closed enum `default` | `wide`, accepted by every profile):
127
+
128
+ ```yaml
129
+ ---
130
+ title: "Architecture Atlas"
131
+ layout: wide # widens --measure-main to 80ch; omit (or 'default') for the standard 65ch
132
+ ---
133
+ ```
134
+
135
+ `Chapter.astro` threads this as `data-layout="wide"` on `<article class="prose">`, and `layout.css` maps `.prose[data-layout="wide"] { --measure-main: 80ch }`. Omitting the field (or setting `layout: default`) emits no attribute and applies no rule — existing chapters render unchanged. A value outside the enum fails the build at content load.
136
+
98
137
  ## See also
99
138
 
100
139
  - `recipes/06-figures.md` — overall figure pipeline + matplotlib/svg sources