@brandon_m_behring/book-scaffold-astro 3.4.0 → 3.5.1

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 { c as BookConfigOptions, f as BookScaffoldIntegrationOptions, y as volatilityLevels } from './types-Bb97Na9S.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 RouteToggles, T as ToolsChapter, i as academicChapterSchema, j as academicParts, k as changeKinds, l as changelogSchema, m as chapterStatus, n as courseNotesChapterSchema, o as defineProfile, p as minimalChapterSchema, q as patternCategories, r as patternsSchema, s as resolvePreset, t as resolveProfile, u as sourceTiers, v as sourcesSchema, w as toolSlugs, x as toolsChapterSchema } from './types-Bb97Na9S.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),
@@ -332,12 +386,33 @@ var courseNotesProfile = defineProfile({
332
386
  styles: ["tokens.css", "layout.css", "callouts.css", "chapter.css", "typography.css", "print.css"]
333
387
  });
334
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
+
335
409
  // src/profiles/index.ts
336
410
  var PROFILES = {
337
411
  academic: academicProfile,
338
412
  tools: toolsProfile,
339
413
  minimal: minimalProfile,
340
- "course-notes": courseNotesProfile
414
+ "course-notes": courseNotesProfile,
415
+ "research-portfolio": researchPortfolioProfile
341
416
  };
342
417
  var BOOK_PROFILES = Object.keys(PROFILES);
343
418
 
@@ -641,9 +716,11 @@ export {
641
716
  minimalChapterSchema,
642
717
  patternCategories,
643
718
  patternsSchema,
719
+ researchPortfolioChapterSchema,
644
720
  resolvePreset,
645
721
  resolveProfile,
646
722
  sourceTiers,
723
+ sourceTiersResearch,
647
724
  sourcesSchema,
648
725
  toolSlugs,
649
726
  toolsChapterSchema,
package/dist/schemas.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { defineCollection } from 'astro:content';
2
- import { g as BookSchemasOptions } from './types-Bb97Na9S.js';
2
+ import { g as BookSchemasOptions } from './types-CzXybcNA.js';
3
3
  import 'astro';
4
4
  import 'astro/zod';
5
5
 
package/dist/schemas.mjs CHANGED
@@ -77,6 +77,7 @@ var toolsChapterSchema = z.object({
77
77
  updated: z.date().optional()
78
78
  });
79
79
  var minimalChapterSchema = toolsChapterSchema;
80
+ var sourceTiersResearch = ["T1", "T2", "T3", "T4"];
80
81
  var courseNotesChapterSchema = z.object({
81
82
  // Identity
82
83
  title: z.string().min(1),
@@ -102,6 +103,59 @@ var courseNotesChapterSchema = z.object({
102
103
  sources: z.array(z.string()).default([]),
103
104
  draft: z.boolean().default(false)
104
105
  });
106
+ var researchPortfolioChapterSchema = z.object({
107
+ // Identity
108
+ title: z.string().min(1),
109
+ slug: z.string().optional(),
110
+ // explicit slug override (otherwise filename)
111
+ description: z.string().optional(),
112
+ // Hierarchy — accept either academic-style or tools-style; all optional.
113
+ // The academic 'part' field is a string enum; tools 'part' is a number.
114
+ // Use z.union to permit either type.
115
+ part: z.union([z.number().int().min(0).max(20), z.string()]).optional(),
116
+ week: z.number().int().min(0).max(99).optional(),
117
+ chapter: z.number().int().min(0).max(99).optional(),
118
+ // Academic-style status (optional for research-portfolio — books may track
119
+ // chapters as 'prose_only' / 'experimental-result' / etc.).
120
+ status: z.enum([
121
+ "implemented",
122
+ "chapter_only",
123
+ "reading_only",
124
+ "prose_only",
125
+ "code_only",
126
+ "scaffolded",
127
+ "planned"
128
+ ]).optional(),
129
+ // Research-portfolio specific: nature of the chapter's content.
130
+ // Distinct from academic's 'status' (which tracks authoring state) — this
131
+ // describes the EVIDENCE TYPE the chapter rests on.
132
+ freshness: z.enum([
133
+ "experimental-result",
134
+ // primary data the author produced
135
+ "literature-survey",
136
+ // synthesis of others' work
137
+ "theoretical",
138
+ // analytical / mathematical argument
139
+ "reference"
140
+ // canonical material (definitions, taxonomy)
141
+ ]).optional(),
142
+ // Provenance (tools-style — overlap with tools/course-notes profiles).
143
+ volatility: z.enum(volatilityLevels).optional(),
144
+ tags: z.array(z.string()).default([]),
145
+ // freeform; replaces tools_compared
146
+ // Structured inline sources with T1-T4 tiers.
147
+ sources: z.array(
148
+ z.object({
149
+ tier: z.enum(sourceTiersResearch),
150
+ url: z.string().url(),
151
+ label: z.string().min(1)
152
+ })
153
+ ).default([]),
154
+ // Status + dates.
155
+ last_verified: z.date(),
156
+ updated: z.date().optional(),
157
+ draft: z.boolean().default(false)
158
+ });
105
159
  var sourcesSchema = z.object({
106
160
  url: z.string().url(),
107
161
  title: z.string().min(1),
@@ -217,12 +271,33 @@ var courseNotesProfile = defineProfile({
217
271
  styles: ["tokens.css", "layout.css", "callouts.css", "chapter.css", "typography.css", "print.css"]
218
272
  });
219
273
 
274
+ // src/profiles/research-portfolio.ts
275
+ var researchPortfolioProfile = defineProfile({
276
+ name: "research-portfolio",
277
+ schema: researchPortfolioChapterSchema,
278
+ routes: {
279
+ references: true,
280
+ search: true,
281
+ print: true,
282
+ chapters: false,
283
+ // portfolio books ship their own landing/index
284
+ convergence: false,
285
+ // tools-profile-specific
286
+ frontmatter: true
287
+ // portfolios universally need title/disclosure/banner pages
288
+ },
289
+ styles: ["tokens.css", "layout.css", "callouts.css", "chapter.css", "typography.css", "print.css"],
290
+ katex: true
291
+ // math is common in research content
292
+ });
293
+
220
294
  // src/profiles/index.ts
221
295
  var PROFILES = {
222
296
  academic: academicProfile,
223
297
  tools: toolsProfile,
224
298
  minimal: minimalProfile,
225
- "course-notes": courseNotesProfile
299
+ "course-notes": courseNotesProfile,
300
+ "research-portfolio": researchPortfolioProfile
226
301
  };
227
302
  var BOOK_PROFILES = Object.keys(PROFILES);
228
303
 
@@ -291,7 +366,7 @@ function frontmatterCollection(schema, base = "./src/content/frontmatter") {
291
366
  function defineBookSchemas(opts = {}) {
292
367
  const profile = resolvePreset(opts.preset, opts.profile);
293
368
  const chaptersBase = opts.chaptersBase ?? "./src/content/chapters";
294
- const schemaForProfile = profile === "academic" ? academicChapterSchema : profile === "course-notes" ? courseNotesChapterSchema : profile === "minimal" ? minimalChapterSchema : toolsChapterSchema;
369
+ const schemaForProfile = profile === "academic" ? academicChapterSchema : profile === "course-notes" ? courseNotesChapterSchema : profile === "research-portfolio" ? researchPortfolioChapterSchema : profile === "minimal" ? minimalChapterSchema : toolsChapterSchema;
295
370
  const chapters = defineCollection({
296
371
  loader: glob({
297
372
  // Exclude underscore-prefixed files (standard "hidden" convention).
@@ -0,0 +1,79 @@
1
+ ---
2
+ title: "Chapter N — Title goes here"
3
+ slug: chN-short-slug
4
+ chapter: 1
5
+ part: 1
6
+ week: 1 # optional; omit if not on a weekly cadence
7
+ status: prose_only # 'prose_only' | 'code_only' | 'implemented' | ...
8
+ freshness: experimental-result # 'experimental-result' | 'literature-survey' | 'theoretical' | 'reference'
9
+ volatility: feature-surface # 'stable-principle' | 'architectural-pattern' | 'feature-surface'
10
+ tags:
11
+ - replace-me
12
+ - with
13
+ - real-tags
14
+ sources:
15
+ - tier: T1
16
+ url: https://example.invalid/primary-source
17
+ label: Primary source (e.g., NVD CVE / arXiv paper / official spec)
18
+ - tier: T2
19
+ url: https://example.invalid/secondary
20
+ label: Secondary corroboration
21
+ last_verified: 2026-05-19
22
+ draft: true
23
+ ---
24
+
25
+ import PreReleaseBanner from '@brandon_m_behring/book-scaffold-astro/components/PreReleaseBanner.astro';
26
+ import PolicyRef from '@brandon_m_behring/book-scaffold-astro/components/PolicyRef.astro';
27
+ import AICollaborationDisclosure from '@brandon_m_behring/book-scaffold-astro/components/AICollaborationDisclosure.astro';
28
+ import BlockedByCallout from '@brandon_m_behring/book-scaffold-astro/components/BlockedByCallout.astro';
29
+ import Theorem from '@brandon_m_behring/book-scaffold-astro/components/Theorem.astro';
30
+ import Sidenote from '@brandon_m_behring/book-scaffold-astro/components/Sidenote.astro';
31
+ import Cite from '@brandon_m_behring/book-scaffold-astro/components/Cite.astro';
32
+
33
+ <PreReleaseBanner state="alpha" />
34
+
35
+ ## Introduction
36
+
37
+ This chapter template exercises the four research-portfolio-specific components.
38
+ Delete sections that don't apply to your chapter.
39
+
40
+ ## Section: a result with provenance
41
+
42
+ Per <Cite key="example2024" />, the result holds under conditions $\Phi \subset \mathbb{R}^n$.
43
+ <Sidenote>Sidenotes float to the right margin on desktop and inline below 768px.</Sidenote>
44
+
45
+ <Theorem kind="theorem" n="1" name="Existence">
46
+ Let $f: X \to Y$ be a map satisfying ... Then there exists ...
47
+ </Theorem>
48
+
49
+ ## Section: policy + AI disclosure references
50
+
51
+ See <PolicyRef file="ETHICS.md" section="§1 Dual-use disclosure" label="our ethics policy" />
52
+ for the dual-use review process applied to this chapter.
53
+
54
+ <AICollaborationDisclosure
55
+ model="Claude Opus 4.7 (Anthropic)"
56
+ role="research collaborator + writing collaborator"
57
+ commit_attribution="Co-Authored-By: Claude <noreply@anthropic.com>"
58
+ >
59
+ All factual claims and numeric results in this chapter independently verified
60
+ by the human author; AI contributions reviewed line-by-line before merge.
61
+ </AICollaborationDisclosure>
62
+
63
+ ## Section: an upstream blocker
64
+
65
+ <BlockedByCallout
66
+ upstream="dataset X v2.0"
67
+ url="https://example.invalid/dataset-x/issues/42"
68
+ reason="full evaluation suite (current: smoke subset only)"
69
+ unblockedAt="2026-Q3"
70
+ >
71
+ This section's numerical claims will be re-verified once dataset X v2.0 ships
72
+ with the missing splits. Current numbers are from the smoke subset and should
73
+ be treated as upper bounds.
74
+ </BlockedByCallout>
75
+
76
+ ## Section: conclusion
77
+
78
+ Wrap up here. Common pattern: 1-2 sentences summarizing the result, 1 sentence
79
+ on limitations, 1 sentence pointing at the next chapter.
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": "3.4.0",
4
+ "version": "3.5.1",
5
5
  "type": "module",
6
6
  "license": "MIT",
7
7
  "author": "Brandon Behring",
@@ -41,6 +41,8 @@
41
41
  "import": "./dist/schemas.mjs"
42
42
  },
43
43
  "./package.json": "./package.json",
44
+ "./components/AICollaborationDisclosure.astro": "./components/AICollaborationDisclosure.astro",
45
+ "./components/BlockedByCallout.astro": "./components/BlockedByCallout.astro",
44
46
  "./components/CaseStudy.astro": "./components/CaseStudy.astro",
45
47
  "./components/ChapterHeader.astro": "./components/ChapterHeader.astro",
46
48
  "./components/ChapterNav.astro": "./components/ChapterNav.astro",
@@ -63,6 +65,8 @@
63
65
  "./components/OpenQuestion.astro": "./components/OpenQuestion.astro",
64
66
  "./components/PaperBox.astro": "./components/PaperBox.astro",
65
67
  "./components/PatternTimeline.astro": "./components/PatternTimeline.astro",
68
+ "./components/PolicyRef.astro": "./components/PolicyRef.astro",
69
+ "./components/PreReleaseBanner.astro": "./components/PreReleaseBanner.astro",
66
70
  "./components/Recovery.astro": "./components/Recovery.astro",
67
71
  "./components/ResultBox.astro": "./components/ResultBox.astro",
68
72
  "./components/Sidebar.astro": "./components/Sidebar.astro",
@@ -114,7 +118,8 @@
114
118
  "examples",
115
119
  "CLAUDE.md",
116
120
  "README.md",
117
- "LATEX_TO_MDX_MAPPING.md"
121
+ "LATEX_TO_MDX_MAPPING.md",
122
+ "examples"
118
123
  ],
119
124
  "scripts": {
120
125
  "build": "tsup && rm -f dist/types-*.d.ts",
@@ -0,0 +1,179 @@
1
+ # Recipe 13 — Research-portfolio getting started
2
+
3
+ The `research-portfolio` preset (v3.5.0+) is for books that combine:
4
+
5
+ - **Academic structure**: week/part/status, KaTeX math, BibTeX citations, Theorem family
6
+ - **Tools-style provenance**: volatility class, T1–T4 tier-tagged sources, `last_verified` freshness
7
+ - **Portfolio-specific affordances**: pre-release banner, AI collaboration disclosure, blocked-by-upstream callouts, structured ethics/policy references
8
+
9
+ If your book is primarily a weekly curriculum, use [`academic`](07-chapter-shapes.md#academic). If primarily AI-CLI comparison content, use [`tools`](07-chapter-shapes.md#tools). If a course-derived study notebook, use [`course-notes`](07-chapter-shapes.md#course-notes). Research portfolios sit at the intersection of all three and get their own preset.
10
+
11
+ ## When to use this preset
12
+
13
+ Choose `research-portfolio` if your book:
14
+
15
+ - Reports on research the author conducted directly (experimental results, theoretical analysis, literature surveys with original synthesis)
16
+ - Has an evolving release state — chapters land at different times, the book passes through alpha → beta → rc states
17
+ - Cites primary sources directly inline per-chapter (vs the tools-profile pattern of a central sources collection)
18
+ - Discloses AI collaboration / dual-use considerations / governance per repo policy
19
+ - Tracks upstream blockers (waiting on a tool release, a paper publication, a dataset)
20
+
21
+ Reference (forthcoming) consumer: [`prompt-injection-portfolio`](https://github.com/brandon-behring/prompt-injection-portfolio).
22
+
23
+ ## Quickstart
24
+
25
+ ```bash
26
+ npx @brandon_m_behring/create-book my-portfolio --preset=research-portfolio
27
+ cd my-portfolio
28
+ npm install
29
+ npm run dev
30
+ ```
31
+
32
+ This scaffolds:
33
+
34
+ - `astro.config.mjs` with `defineBookConfig({ preset: 'research-portfolio' })`
35
+ - `src/content.config.ts` with the `researchPortfolioChapterSchema`
36
+ - A sample chapter at `src/content/chapters/01-introduction.mdx`
37
+ - Frontmatter pages at `src/content/frontmatter/` (title-page, ai-disclosure, banner)
38
+ - A `bibliography.bib` stub for citations
39
+
40
+ ## Chapter frontmatter shape
41
+
42
+ ```yaml
43
+ ---
44
+ title: "Chapter title"
45
+ slug: ch01-introduction # optional; defaults to filename
46
+ chapter: 1 # tools-style numeric
47
+ part: 1 # either number OR academic-style string enum
48
+ week: 1 # optional; only if you use weekly cadence
49
+ status: prose_only # academic 7-state (optional)
50
+ freshness: experimental-result # 'experimental-result' | 'literature-survey' | 'theoretical' | 'reference'
51
+ volatility: feature-surface # tools-style: 'stable-principle' | 'architectural-pattern' | 'feature-surface'
52
+ tags: # freeform string array (NOT the tools_compared enum)
53
+ - prompt-injection
54
+ - red-team
55
+ - CVE-2025-32711
56
+ sources:
57
+ - tier: T1
58
+ url: https://nvd.nist.gov/vuln/detail/CVE-2025-32711
59
+ label: NVD CVE-2025-32711 (primary advisory)
60
+ - tier: T2
61
+ url: https://arxiv.org/abs/2406.00799
62
+ label: TaskTracker (Wallace et al. 2024)
63
+ last_verified: 2026-05-19
64
+ draft: false
65
+ ---
66
+ ```
67
+
68
+ All hierarchy fields (`part`, `week`, `chapter`) are optional — chapters can use whichever shape fits. The route templates dispatch on which is set.
69
+
70
+ ## The 4 portfolio-specific components
71
+
72
+ Shipped in v3.5.0 alongside the preset:
73
+
74
+ ### `<PreReleaseBanner>` — declare release state
75
+
76
+ ```astro
77
+ ---
78
+ import PreReleaseBanner from '@brandon_m_behring/book-scaffold-astro/components/PreReleaseBanner.astro';
79
+ ---
80
+ <PreReleaseBanner state="alpha" />
81
+ <PreReleaseBanner state="beta" dismissAt="v0.7.0" />
82
+ <PreReleaseBanner state="rc" message="Final review pass; please file issues." />
83
+ <PreReleaseBanner state="locked" />
84
+ ```
85
+
86
+ Place at the top of a layout to surface site-wide, or inline at the top of a specific chapter. Four states: `'alpha' | 'beta' | 'rc' | 'locked'`. Each has a default message + color treatment; override with `message`.
87
+
88
+ ### `<PolicyRef>` — inline link to a repo-root policy doc
89
+
90
+ ```astro
91
+ ---
92
+ import PolicyRef from '@brandon_m_behring/book-scaffold-astro/components/PolicyRef.astro';
93
+ ---
94
+ See <PolicyRef file="ETHICS.md" section="§1 Dual-use disclosure" label="our ethics policy" />
95
+ for the dual-use review process.
96
+
97
+ Per <PolicyRef file="SECURITY.md" /> the disclosure timeline is 90 days.
98
+ ```
99
+
100
+ Resolves to `/<file>#<slug-of-section>` by default (assumes consumer ships the markdown at site root via `public/` or an Astro page). Override the href with `href="..."`.
101
+
102
+ ### `<AICollaborationDisclosure>` — render an AI-collab paragraph
103
+
104
+ ```astro
105
+ ---
106
+ import AICollaborationDisclosure from '@brandon_m_behring/book-scaffold-astro/components/AICollaborationDisclosure.astro';
107
+ ---
108
+ <AICollaborationDisclosure
109
+ model="Claude Opus 4.7 + Sonnet 4.6 (Anthropic)"
110
+ role="research collaborator + writing collaborator"
111
+ commit_attribution="Co-Authored-By: Claude <noreply@anthropic.com>"
112
+ >
113
+ All factual claims independently verified by the human author; AI contributions
114
+ reviewed line-by-line before merge.
115
+ </AICollaborationDisclosure>
116
+ ```
117
+
118
+ Three required props (`model`, `role`, `commit_attribution`); optional slot for prose. For YAML-driven config, load the YAML consumer-side and spread props:
119
+
120
+ ```astro
121
+ ---
122
+ import disclosure from '../data/ai-collaboration.yaml';
123
+ ---
124
+ <AICollaborationDisclosure {...disclosure} />
125
+ ```
126
+
127
+ (The scaffold doesn't bundle a YAML parser; use `astro:content` file loader with `yaml()` or similar consumer-side.)
128
+
129
+ ### `<BlockedByCallout>` — declare upstream blockers
130
+
131
+ ```astro
132
+ ---
133
+ import BlockedByCallout from '@brandon_m_behring/book-scaffold-astro/components/BlockedByCallout.astro';
134
+ ---
135
+ <BlockedByCallout
136
+ upstream="book-scaffold-astro v3.5.0"
137
+ url="https://github.com/brandon-behring/book-scaffold-astro/issues/6"
138
+ reason="research-portfolio preset + 3 new components"
139
+ unblockedAt="2026-05-19"
140
+ >
141
+ Once the preset ships, this chapter's frontmatter migrates from the
142
+ hand-rolled schema to the upstream `research-portfolio` shape.
143
+ </BlockedByCallout>
144
+ ```
145
+
146
+ Use for chapters/sections waiting on external work — a tool release, a paper publication, a dataset acquisition. The structured fields produce a scannable card; slot content holds migration notes / workaround prose.
147
+
148
+ ## Frontmatter pages
149
+
150
+ The `research-portfolio` preset enables `/frontmatter/[slug]/` by default. Drop MDX files under `src/content/frontmatter/` (each needs `slug`, `title`, `order` per `frontmatterCollection()` — see [recipe 04](04-component-library.md) or PACKAGE_DESIGN.md §17).
151
+
152
+ Common frontmatter pages for a portfolio:
153
+
154
+ - `title-page.mdx` — book title + author + version + license
155
+ - `ai-collaboration-disclosure.mdx` — wraps `<AICollaborationDisclosure>`
156
+ - `pre-alpha-banner.mdx` (or similar) — author's note on release state
157
+ - `executive-summary.mdx` — 1-page overview for skim readers
158
+ - `acknowledgments.mdx` — collaborators + funding + dataset providers
159
+ - `ethics-policy.mdx` — wraps `<PolicyRef>` to other ETHICS docs
160
+
161
+ ## Migrating from a hand-rolled schema
162
+
163
+ If you previously rolled your own schema (e.g., for `prompt-injection-portfolio` pre-v3.5.0), migration is mostly mechanical:
164
+
165
+ 1. **Replace your `defineCollection` for chapters** with `defineBookSchemas({ preset: 'research-portfolio' }).collections.chapters`.
166
+ 2. **Rename `tools_compared` → `tags`** in frontmatter across chapters (the new schema uses freeform `tags`; the rename is a global find-and-replace).
167
+ 3. **Restructure `sources`** to the new inline shape `{ tier: 'T1', url, label }` — if you were using the tools-profile `sources` collection (referenced by string keys), inline them per chapter.
168
+ 4. **Replace ad-hoc PreReleaseBanner / EthicsRef / AIAssistanceDisclosure** components with the scaffold-shipped versions (delete your local copies; update imports).
169
+ 5. **Bump pin to `^3.5.0`** in your `package.json`.
170
+
171
+ See `package/CHANGELOG.md` §3.5.0 for the full additive list.
172
+
173
+ ## See also
174
+
175
+ - [Recipe 04 — Component library](04-component-library.md) — full component reference (38+ now with v3.5.0 additions)
176
+ - [Recipe 07 — Chapter shapes](07-chapter-shapes.md) — choosing between presets
177
+ - [Recipe 12 — Where to file issues](12-where-to-file-issues.md) — feedback loop for new portfolios
178
+ - [`LATEX_TO_MDX_MAPPING.md`](../LATEX_TO_MDX_MAPPING.md) — converting a LaTeX research book
179
+ - [`PACKAGE_DESIGN.md`](../PACKAGE_DESIGN.md) — full API contract