@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.
- package/components/AICollaborationDisclosure.astro +81 -0
- package/components/BlockedByCallout.astro +82 -0
- package/components/PolicyRef.astro +46 -0
- package/components/PreReleaseBanner.astro +73 -0
- package/dist/index.d.ts +2 -2
- package/dist/index.mjs +78 -1
- package/dist/schemas.d.ts +1 -1
- package/dist/schemas.mjs +77 -2
- package/examples/chapter-template-research-portfolio.mdx +79 -0
- package/package.json +7 -2
- package/recipes/13-research-portfolio-getting-started.md +179 -0
|
@@ -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,
|
|
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,
|
|
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
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
|
+
"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
|