@brandon_m_behring/book-scaffold-astro 3.3.0 → 3.5.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.
@@ -0,0 +1,81 @@
1
+ ---
2
+ /**
3
+ * AICollaborationDisclosure — render a structured AI-collaboration disclosure
4
+ * paragraph from props.
5
+ *
6
+ * v3.5.0 (research-portfolio profile, closes #6). Generic primitive.
7
+ * Research portfolios universally need to disclose AI involvement (per
8
+ * various norms); this component centralizes the rendering so consumers
9
+ * don't reinvent the disclosure block per book.
10
+ *
11
+ * Usage (props form — recommended):
12
+ * <AICollaborationDisclosure
13
+ * model="Claude Opus 4.7 + Sonnet 4.6 (Anthropic)"
14
+ * role="research collaborator + writing collaborator"
15
+ * commit_attribution="Co-Authored-By: Claude <noreply@anthropic.com>"
16
+ * />
17
+ *
18
+ * Usage (YAML-driven — consumer loads via astro:content data collection):
19
+ * ---
20
+ * import disclosure from '../data/ai-collaboration.yaml';
21
+ * ---
22
+ * <AICollaborationDisclosure {...disclosure} />
23
+ *
24
+ * The YAML loader is consumer-side (not shipped) to avoid pulling a YAML
25
+ * dep into the scaffold; Astro consumers commonly use the `astro:content`
26
+ * file loader with `yaml()` or similar.
27
+ *
28
+ * Slots:
29
+ * `default` — optional extra prose to append after the model/role line
30
+ * (e.g. "All factual claims independently verified by …").
31
+ */
32
+ interface Props {
33
+ model: string;
34
+ role: string;
35
+ commit_attribution?: string;
36
+ }
37
+ const { model, role, commit_attribution } = Astro.props;
38
+ ---
39
+ <aside class="ai-collab-disclosure" role="note" aria-labelledby="ai-collab-h">
40
+ <h3 id="ai-collab-h">AI collaboration disclosure</h3>
41
+ <p>
42
+ This book was authored in collaboration with <strong>{model}</strong>, acting as <em>{role}</em>.
43
+ </p>
44
+ {commit_attribution && (
45
+ <p>
46
+ Commits attributed to the model carry the trailer
47
+ <code>{commit_attribution}</code>.
48
+ </p>
49
+ )}
50
+ <slot />
51
+ </aside>
52
+
53
+ <style>
54
+ .ai-collab-disclosure {
55
+ display: block;
56
+ margin: var(--space-6) 0;
57
+ padding: var(--space-3) var(--space-4);
58
+ border-left: var(--border-bar) solid var(--warm-plum);
59
+ background: var(--warm-plum-tint);
60
+ border-radius: 0 var(--radius-md) var(--radius-md) 0;
61
+ font-size: var(--text-sm);
62
+ line-height: var(--leading-normal);
63
+ }
64
+ .ai-collab-disclosure h3 {
65
+ margin: 0 0 var(--space-2);
66
+ font-size: var(--text-base);
67
+ color: var(--warm-plum);
68
+ letter-spacing: 0.02em;
69
+ }
70
+ .ai-collab-disclosure p {
71
+ margin: var(--space-2) 0;
72
+ text-indent: 0;
73
+ }
74
+ .ai-collab-disclosure code {
75
+ font-family: var(--font-code);
76
+ font-size: 0.95em;
77
+ background: var(--color-code-bg);
78
+ padding: 0.1em 0.4em;
79
+ border-radius: var(--radius-sm);
80
+ }
81
+ </style>
@@ -0,0 +1,82 @@
1
+ ---
2
+ /**
3
+ * BlockedByCallout — declare that this chapter / section / experiment is
4
+ * blocked on an upstream dependency (e.g., a tool release, a paper
5
+ * publication, a hardware acquisition).
6
+ *
7
+ * v3.5.0 (research-portfolio profile, closes #6 + the cross-consumer
8
+ * dogfooding pattern). Research books often have "blocked by X" sections
9
+ * — explicit, dated, with optional unblock target.
10
+ *
11
+ * Usage:
12
+ * <BlockedByCallout
13
+ * upstream="book-scaffold-astro v3.5.0"
14
+ * url="https://github.com/brandon-behring/book-scaffold-astro/issues/6"
15
+ * reason="research-portfolio preset + 3 new components"
16
+ * unblockedAt="2026-05-19"
17
+ * >
18
+ * Once the preset ships, this chapter's frontmatter migrates from the
19
+ * hand-rolled schema to the upstream `research-portfolio` shape.
20
+ * </BlockedByCallout>
21
+ *
22
+ * Slot content (optional) appears under the structured fields and is the
23
+ * place to put migration notes / workaround prose.
24
+ */
25
+ interface Props {
26
+ upstream: string;
27
+ reason: string;
28
+ url?: string;
29
+ unblockedAt?: string;
30
+ }
31
+ const { upstream, reason, url, unblockedAt } = Astro.props;
32
+ const upstreamLabel = url ? null : upstream;
33
+ ---
34
+ <aside class="blocked-by-callout" role="note" aria-labelledby="blocked-by-h">
35
+ <h3 id="blocked-by-h">Blocked by upstream</h3>
36
+ <p>
37
+ <strong>
38
+ {url ? <a href={url} rel="external noopener">{upstream}</a> : upstreamLabel}
39
+ </strong>
40
+ {' — '}{reason}{unblockedAt && (<span class="unblocked-at"> (expected {unblockedAt})</span>)}.
41
+ </p>
42
+ <div class="blocked-by-body">
43
+ <slot />
44
+ </div>
45
+ </aside>
46
+
47
+ <style>
48
+ .blocked-by-callout {
49
+ display: block;
50
+ margin: var(--space-4) 0;
51
+ padding: var(--space-3) var(--space-4);
52
+ border-left: var(--border-bar) solid var(--warm-gold);
53
+ background: var(--warm-gold-tint);
54
+ border-radius: 0 var(--radius-md) var(--radius-md) 0;
55
+ font-size: var(--text-sm);
56
+ line-height: var(--leading-normal);
57
+ }
58
+ .blocked-by-callout h3 {
59
+ margin: 0 0 var(--space-2);
60
+ font-size: var(--text-base);
61
+ color: var(--warm-gold);
62
+ letter-spacing: 0.02em;
63
+ }
64
+ .blocked-by-callout p {
65
+ margin: var(--space-2) 0;
66
+ text-indent: 0;
67
+ }
68
+ .blocked-by-callout a {
69
+ color: var(--warm-gold);
70
+ text-decoration: underline;
71
+ }
72
+ .unblocked-at {
73
+ color: var(--color-text-muted);
74
+ font-style: italic;
75
+ }
76
+ .blocked-by-body :global(p:first-child) {
77
+ margin-top: 0;
78
+ }
79
+ .blocked-by-body :global(p:last-child) {
80
+ margin-bottom: 0;
81
+ }
82
+ </style>
@@ -0,0 +1,46 @@
1
+ ---
2
+ /**
3
+ * PolicyRef — inline link to a repo-root policy document.
4
+ *
5
+ * v3.5.0 (research-portfolio profile, closes #6). Generic: any consumer can
6
+ * use it to cite ETHICS.md / SECURITY.md / CODE_OF_CONDUCT.md / LICENSE /
7
+ * GOVERNANCE.md without rebuilding the same "link + label" pattern per book.
8
+ *
9
+ * Usage:
10
+ * <PolicyRef file="ETHICS.md" section="§1 Dual-use disclosure" label="ethics policy" />
11
+ * <PolicyRef file="SECURITY.md" /> // default label = filename
12
+ * <PolicyRef file="GOVERNANCE.md" section="Quorum" /> // auto-anchor from section
13
+ *
14
+ * The href resolves to `/<file>` by default (assuming the consumer ships the
15
+ * markdown at site root via public/ or an Astro page). When `section` is set,
16
+ * a slugified anchor `#<slug>` is appended. The label defaults to the section
17
+ * name (if present) else the filename.
18
+ */
19
+ interface Props {
20
+ file: string;
21
+ section?: string;
22
+ label?: string;
23
+ href?: string; // explicit override (otherwise computed)
24
+ }
25
+ const { file, section, label, href } = Astro.props;
26
+
27
+ const slugify = (s: string) =>
28
+ s.toLowerCase().replace(/[§\W]+/g, '-').replace(/^-+|-+$/g, '');
29
+
30
+ const resolvedHref = href ?? `/${file}${section ? `#${slugify(section)}` : ''}`;
31
+ const display = label ?? (section ? `${file} ${section}` : file);
32
+ ---
33
+ <a class="policy-ref" href={resolvedHref}>{display}</a>
34
+
35
+ <style>
36
+ .policy-ref {
37
+ font-family: var(--font-code);
38
+ font-size: 0.95em;
39
+ color: var(--color-link);
40
+ text-decoration: none;
41
+ border-bottom: 1px dotted var(--color-link);
42
+ }
43
+ .policy-ref:hover {
44
+ border-bottom-style: solid;
45
+ }
46
+ </style>
@@ -0,0 +1,73 @@
1
+ ---
2
+ /**
3
+ * PreReleaseBanner — site-wide banner declaring the book's release state.
4
+ *
5
+ * v3.5.0 (research-portfolio profile, closes #6). Generic primitive; any
6
+ * profile can use it but research portfolios are the primary use case
7
+ * (long-running research books pass through alpha → beta → rc states).
8
+ *
9
+ * Usage:
10
+ * <PreReleaseBanner state="alpha" />
11
+ * <PreReleaseBanner state="beta" dismissAt="v0.7.0" />
12
+ * <PreReleaseBanner state="rc" message="Final review pass; please open issues." />
13
+ * <PreReleaseBanner state="locked" />
14
+ *
15
+ * Drop inside a layout or chapter front-matter file. Common placement is the
16
+ * top of every page via a layout slot — see recipes/13.
17
+ */
18
+ interface Props {
19
+ state: 'alpha' | 'beta' | 'rc' | 'locked';
20
+ dismissAt?: string;
21
+ message?: string;
22
+ }
23
+ const { state, dismissAt, message } = Astro.props;
24
+
25
+ const DEFAULTS: Record<Props['state'], string> = {
26
+ alpha: 'This book is in alpha — expect breaking changes and partial coverage.',
27
+ beta: 'This book is in beta — most sections stable; minor changes possible.',
28
+ rc: 'Release candidate — finalizing content; substantive feedback welcome.',
29
+ locked: 'This release is frozen. See CHANGELOG for the next iteration.',
30
+ };
31
+ const text = message ?? DEFAULTS[state];
32
+ const label = `${state.toUpperCase()}${dismissAt ? ` (until ${dismissAt})` : ''}`;
33
+ ---
34
+ <aside class={`pre-release-banner pre-release-banner-${state}`} role="status" aria-live="polite">
35
+ <strong>{label}:</strong> {text}
36
+ </aside>
37
+
38
+ <style>
39
+ .pre-release-banner {
40
+ display: block;
41
+ margin: var(--space-2) 0 var(--space-4);
42
+ padding: var(--space-2) var(--space-3);
43
+ border-radius: var(--radius-md);
44
+ border-left: var(--border-bar) solid;
45
+ font-size: var(--text-sm);
46
+ line-height: var(--leading-normal);
47
+ }
48
+ .pre-release-banner strong {
49
+ margin-right: var(--space-1);
50
+ font-weight: 600;
51
+ letter-spacing: 0.04em;
52
+ }
53
+ .pre-release-banner-alpha {
54
+ background: var(--warm-rose-tint);
55
+ border-left-color: var(--warm-rose);
56
+ color: var(--warm-rose);
57
+ }
58
+ .pre-release-banner-beta {
59
+ background: var(--warm-gold-tint);
60
+ border-left-color: var(--warm-gold);
61
+ color: var(--warm-gold);
62
+ }
63
+ .pre-release-banner-rc {
64
+ background: var(--warm-blue-tint);
65
+ border-left-color: var(--warm-blue);
66
+ color: var(--warm-blue);
67
+ }
68
+ .pre-release-banner-locked {
69
+ background: var(--warm-green-tint);
70
+ border-left-color: var(--warm-green);
71
+ color: var(--warm-green);
72
+ }
73
+ </style>
package/dist/index.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { AstroUserConfig, AstroIntegration } from 'astro';
2
- import { b as BookConfigOptions, d as BookScaffoldIntegrationOptions, v as volatilityLevels } from './types-s8NxCLU2.js';
3
- export { A as AcademicChapter, B as BOOK_PROFILES, a as BookConfigError, c as BookProfile, e as BookSchemasOptions, C as ChapterFor, f as CourseNotesChapter, M as MinimalChapter, P as ProfileDefinition, R as RouteToggles, T as ToolsChapter, g as academicChapterSchema, h as academicParts, i as changeKinds, j as changelogSchema, k as chapterStatus, l as courseNotesChapterSchema, m as defineProfile, n as minimalChapterSchema, p as patternCategories, o as patternsSchema, r as resolveProfile, s as sourceTiers, q as sourcesSchema, t as toolSlugs, u as toolsChapterSchema } from './types-s8NxCLU2.js';
2
+ import { c as BookConfigOptions, f as BookScaffoldIntegrationOptions, E as volatilityLevels } from './types-CzXybcNA.js';
3
+ export { A as AcademicChapter, B as BOOK_PRESETS, a as BOOK_PROFILES, b as BookConfigError, d as BookPreset, e as BookProfile, g as BookSchemasOptions, C as ChapterFor, h as CourseNotesChapter, M as MinimalChapter, P as ProfileDefinition, R as ResearchPortfolioChapter, i as RouteToggles, T as ToolsChapter, j as academicChapterSchema, k as academicParts, l as changeKinds, m as changelogSchema, n as chapterStatus, o as courseNotesChapterSchema, p as defineProfile, q as minimalChapterSchema, r as patternCategories, s as patternsSchema, t as researchPortfolioChapterSchema, u as resolvePreset, v as resolveProfile, w as sourceTiers, x as sourceTiersResearch, y as sourcesSchema, z as toolSlugs, D as toolsChapterSchema } from './types-CzXybcNA.js';
4
4
  import 'astro/zod';
5
5
 
6
6
  declare function defineBookConfig(opts: BookConfigOptions): Promise<AstroUserConfig>;
package/dist/index.mjs CHANGED
@@ -192,6 +192,7 @@ var toolsChapterSchema = z.object({
192
192
  updated: z.date().optional()
193
193
  });
194
194
  var minimalChapterSchema = toolsChapterSchema;
195
+ var sourceTiersResearch = ["T1", "T2", "T3", "T4"];
195
196
  var courseNotesChapterSchema = z.object({
196
197
  // Identity
197
198
  title: z.string().min(1),
@@ -217,6 +218,59 @@ var courseNotesChapterSchema = z.object({
217
218
  sources: z.array(z.string()).default([]),
218
219
  draft: z.boolean().default(false)
219
220
  });
221
+ var researchPortfolioChapterSchema = z.object({
222
+ // Identity
223
+ title: z.string().min(1),
224
+ slug: z.string().optional(),
225
+ // explicit slug override (otherwise filename)
226
+ description: z.string().optional(),
227
+ // Hierarchy — accept either academic-style or tools-style; all optional.
228
+ // The academic 'part' field is a string enum; tools 'part' is a number.
229
+ // Use z.union to permit either type.
230
+ part: z.union([z.number().int().min(0).max(20), z.string()]).optional(),
231
+ week: z.number().int().min(0).max(99).optional(),
232
+ chapter: z.number().int().min(0).max(99).optional(),
233
+ // Academic-style status (optional for research-portfolio — books may track
234
+ // chapters as 'prose_only' / 'experimental-result' / etc.).
235
+ status: z.enum([
236
+ "implemented",
237
+ "chapter_only",
238
+ "reading_only",
239
+ "prose_only",
240
+ "code_only",
241
+ "scaffolded",
242
+ "planned"
243
+ ]).optional(),
244
+ // Research-portfolio specific: nature of the chapter's content.
245
+ // Distinct from academic's 'status' (which tracks authoring state) — this
246
+ // describes the EVIDENCE TYPE the chapter rests on.
247
+ freshness: z.enum([
248
+ "experimental-result",
249
+ // primary data the author produced
250
+ "literature-survey",
251
+ // synthesis of others' work
252
+ "theoretical",
253
+ // analytical / mathematical argument
254
+ "reference"
255
+ // canonical material (definitions, taxonomy)
256
+ ]).optional(),
257
+ // Provenance (tools-style — overlap with tools/course-notes profiles).
258
+ volatility: z.enum(volatilityLevels).optional(),
259
+ tags: z.array(z.string()).default([]),
260
+ // freeform; replaces tools_compared
261
+ // Structured inline sources with T1-T4 tiers.
262
+ sources: z.array(
263
+ z.object({
264
+ tier: z.enum(sourceTiersResearch),
265
+ url: z.string().url(),
266
+ label: z.string().min(1)
267
+ })
268
+ ).default([]),
269
+ // Status + dates.
270
+ last_verified: z.date(),
271
+ updated: z.date().optional(),
272
+ draft: z.boolean().default(false)
273
+ });
220
274
  var sourcesSchema = z.object({
221
275
  url: z.string().url(),
222
276
  title: z.string().min(1),
@@ -263,8 +317,10 @@ var academicProfile = defineProfile({
263
317
  print: true,
264
318
  chapters: false,
265
319
  // academic consumers ship their own week-based /chapters listing
266
- convergence: false
320
+ convergence: false,
267
321
  // tools-profile-specific
322
+ frontmatter: false
323
+ // opt-in per book; see #7
268
324
  },
269
325
  styles: ["tokens.css", "layout.css", "callouts.css", "chapter.css", "typography.css", "print.css"],
270
326
  katex: true
@@ -280,8 +336,10 @@ var toolsProfile = defineProfile({
280
336
  print: true,
281
337
  chapters: true,
282
338
  // tools profile ships a flat chapter index
283
- convergence: true
339
+ convergence: true,
284
340
  // tools profile ships convergence dashboard
341
+ frontmatter: false
342
+ // opt-in per book; see #7
285
343
  },
286
344
  styles: [
287
345
  "tokens.css",
@@ -304,7 +362,9 @@ var minimalProfile = defineProfile({
304
362
  search: true,
305
363
  print: true,
306
364
  chapters: false,
307
- convergence: false
365
+ convergence: false,
366
+ frontmatter: false
367
+ // opt-in per book; see #7
308
368
  },
309
369
  styles: ["tokens.css", "layout.css", "callouts.css", "chapter.css", "typography.css", "print.css"]
310
370
  });
@@ -319,22 +379,46 @@ var courseNotesProfile = defineProfile({
319
379
  print: true,
320
380
  chapters: false,
321
381
  // multi-book consumers route via [book]/[slug] themselves
322
- convergence: false
382
+ convergence: false,
383
+ frontmatter: false
384
+ // opt-in per book; see #7
323
385
  },
324
386
  styles: ["tokens.css", "layout.css", "callouts.css", "chapter.css", "typography.css", "print.css"]
325
387
  });
326
388
 
389
+ // src/profiles/research-portfolio.ts
390
+ var researchPortfolioProfile = defineProfile({
391
+ name: "research-portfolio",
392
+ schema: researchPortfolioChapterSchema,
393
+ routes: {
394
+ references: true,
395
+ search: true,
396
+ print: true,
397
+ chapters: false,
398
+ // portfolio books ship their own landing/index
399
+ convergence: false,
400
+ // tools-profile-specific
401
+ frontmatter: true
402
+ // portfolios universally need title/disclosure/banner pages
403
+ },
404
+ styles: ["tokens.css", "layout.css", "callouts.css", "chapter.css", "typography.css", "print.css"],
405
+ katex: true
406
+ // math is common in research content
407
+ });
408
+
327
409
  // src/profiles/index.ts
328
410
  var PROFILES = {
329
411
  academic: academicProfile,
330
412
  tools: toolsProfile,
331
413
  minimal: minimalProfile,
332
- "course-notes": courseNotesProfile
414
+ "course-notes": courseNotesProfile,
415
+ "research-portfolio": researchPortfolioProfile
333
416
  };
334
417
  var BOOK_PROFILES = Object.keys(PROFILES);
335
418
 
336
419
  // src/types.ts
337
420
  import { existsSync, readFileSync } from "fs";
421
+ var BOOK_PRESETS = BOOK_PROFILES;
338
422
  var BookConfigError = class extends Error {
339
423
  constructor(message) {
340
424
  super(message);
@@ -359,29 +443,33 @@ function readEnvFile(path = ".env") {
359
443
  return {};
360
444
  }
361
445
  }
362
- function resolveProfile(explicit) {
363
- let candidate = explicit ?? process.env.BOOK_PROFILE;
446
+ function resolvePreset(explicitPreset, explicitProfile) {
447
+ let candidate = explicitPreset ?? explicitProfile ?? process.env.BOOK_PRESET ?? process.env.BOOK_PROFILE;
364
448
  let source = "default";
365
- if (explicit) source = "param";
366
- else if (process.env.BOOK_PROFILE) source = "env";
449
+ if (explicitPreset || explicitProfile) source = "param";
450
+ else if (process.env.BOOK_PRESET || process.env.BOOK_PROFILE) source = "env";
367
451
  if (!candidate) {
368
- const fromFile = readEnvFile().BOOK_PROFILE;
452
+ const env = readEnvFile();
453
+ const fromFile = env.BOOK_PRESET ?? env.BOOK_PROFILE;
369
454
  if (fromFile) {
370
455
  candidate = fromFile;
371
456
  source = "dotenv";
372
457
  }
373
458
  }
374
459
  candidate = candidate ?? "minimal";
375
- if (!BOOK_PROFILES.includes(candidate)) {
460
+ if (!BOOK_PRESETS.includes(candidate)) {
376
461
  throw new BookConfigError(
377
- `profile must be one of ${BOOK_PROFILES.join(" | ")} (got ${JSON.stringify(candidate)})`
462
+ `preset must be one of ${BOOK_PRESETS.join(" | ")} (got ${JSON.stringify(candidate)})`
378
463
  );
379
464
  }
380
465
  if (source === "default") {
381
- console.warn("book-scaffold-astro: BOOK_PROFILE not set; falling back to 'minimal'.");
466
+ console.warn("book-scaffold-astro: BOOK_PRESET not set; falling back to 'minimal'.");
382
467
  }
383
468
  return candidate;
384
469
  }
470
+ function resolveProfile(explicit) {
471
+ return resolvePreset(void 0, explicit);
472
+ }
385
473
 
386
474
  // src/integration.ts
387
475
  import { fileURLToPath } from "url";
@@ -435,7 +523,11 @@ var ROUTE_REGISTRY = {
435
523
  search: { pattern: "/search", file: "search.astro" },
436
524
  print: { pattern: "/print", file: "print.astro" },
437
525
  chapters: { pattern: "/chapters", file: "chapters.astro" },
438
- convergence: { pattern: "/convergence", file: "convergence.astro" }
526
+ convergence: { pattern: "/convergence", file: "convergence.astro" },
527
+ // v3.4.0 (#7): consumer-collection-backed frontmatter route. Opt-in via
528
+ // routes: { frontmatter: true } AND content.config.ts defining the
529
+ // collection (use frontmatterCollection() helper from /schemas subpath).
530
+ frontmatter: { pattern: "/frontmatter/[slug]", file: "frontmatter/[...slug].astro" }
439
531
  };
440
532
  function resolvePage(file) {
441
533
  return fileURLToPath(new URL(`../pages/${file}`, import.meta.url));
@@ -466,9 +558,14 @@ function bookScaffoldIntegration(opts) {
466
558
  }
467
559
  const consumerRoot = fileURLToPath(config.root);
468
560
  const resolvedMdxPath = resolveMdxComponentsPath(consumerRoot, mdxComponentsModule);
561
+ const presetLiteral = JSON.stringify(profile);
469
562
  updateConfig({
470
563
  vite: {
471
- plugins: [makeMdxComponentsVitePlugin(resolvedMdxPath)]
564
+ plugins: [makeMdxComponentsVitePlugin(resolvedMdxPath)],
565
+ define: {
566
+ "import.meta.env.BOOK_PRESET": presetLiteral,
567
+ "import.meta.env.BOOK_PROFILE": presetLiteral
568
+ }
472
569
  }
473
570
  });
474
571
  }
@@ -478,7 +575,7 @@ function bookScaffoldIntegration(opts) {
478
575
 
479
576
  // src/config.ts
480
577
  async function defineBookConfig(opts) {
481
- const profile = resolveProfile(opts.profile);
578
+ const profile = resolvePreset(opts.preset, opts.profile);
482
579
  const remarkPlugins = [];
483
580
  const rehypePlugins = [];
484
581
  if (profile === "academic") {
@@ -531,6 +628,8 @@ async function defineBookConfig(opts) {
531
628
  ...userMarkdown
532
629
  };
533
630
  const {
631
+ preset: _preset,
632
+ // v3.4.0
534
633
  profile: _profile,
535
634
  routes: _routes,
536
635
  // v3.3.0
@@ -541,6 +640,7 @@ async function defineBookConfig(opts) {
541
640
  markdown: _markdown,
542
641
  ...rest
543
642
  } = opts;
643
+ void _preset;
544
644
  void _profile;
545
645
  void _routes;
546
646
  void _mdxComponentsModule;
@@ -598,6 +698,7 @@ function freshnessLabel(f) {
598
698
  }
599
699
  }
600
700
  export {
701
+ BOOK_PRESETS,
601
702
  BOOK_PROFILES,
602
703
  BookConfigError,
603
704
  academicChapterSchema,
@@ -615,8 +716,11 @@ export {
615
716
  minimalChapterSchema,
616
717
  patternCategories,
617
718
  patternsSchema,
719
+ researchPortfolioChapterSchema,
720
+ resolvePreset,
618
721
  resolveProfile,
619
722
  sourceTiers,
723
+ sourceTiersResearch,
620
724
  sourcesSchema,
621
725
  toolSlugs,
622
726
  toolsChapterSchema,
package/dist/schemas.d.ts CHANGED
@@ -1,7 +1,40 @@
1
- import { e as BookSchemasOptions } from './types-s8NxCLU2.js';
1
+ import { defineCollection } from 'astro:content';
2
+ import { g as BookSchemasOptions } from './types-CzXybcNA.js';
2
3
  import 'astro';
3
4
  import 'astro/zod';
4
5
 
6
+ /**
7
+ * v3.4.0 (closes #7): consumer-facing helper to define a `frontmatter`
8
+ * content collection that the scaffold's auto-injected
9
+ * `/frontmatter/[slug]` route can render.
10
+ *
11
+ * Usage in consumer's content.config.ts:
12
+ *
13
+ * import { defineBookSchemas, frontmatterCollection } from
14
+ * '@brandon_m_behring/book-scaffold-astro/schemas';
15
+ * import { z } from 'astro:content';
16
+ *
17
+ * export const { collections } = {
18
+ * collections: {
19
+ * ...defineBookSchemas().collections,
20
+ * frontmatter: frontmatterCollection(z.object({
21
+ * slug: z.string(),
22
+ * title: z.string(),
23
+ * order: z.number(),
24
+ * description: z.string().optional(),
25
+ * })),
26
+ * },
27
+ * };
28
+ *
29
+ * Then enable the route via `defineBookConfig({ routes: { frontmatter: true } })`
30
+ * and drop MDX files under `src/content/frontmatter/`. The scaffold-injected
31
+ * route renders each entry with the consumer's mdx-components in scope (issue #2
32
+ * plumbing applies).
33
+ *
34
+ * Default loader: `**\/*.{md,mdx}` under `./src/content/frontmatter` (excluding
35
+ * underscore-prefixed files). Override `base` via the second arg.
36
+ */
37
+ declare function frontmatterCollection(schema: Parameters<typeof defineCollection>[0]['schema'], base?: string): unknown;
5
38
  /**
6
39
  * Returns the package's default content collections. Closed shape per Q5;
7
40
  * consumer extends via object spread and Zod `.extend()` (see PACKAGE_DESIGN.md §5).
@@ -10,4 +43,4 @@ declare function defineBookSchemas(opts?: BookSchemasOptions): {
10
43
  collections: Record<string, unknown>;
11
44
  };
12
45
 
13
- export { defineBookSchemas };
46
+ export { defineBookSchemas, frontmatterCollection };