@brandon_m_behring/book-scaffold-astro 4.8.0 → 4.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CLAUDE.md CHANGED
@@ -25,6 +25,8 @@ When in doubt, run `grep BOOK_PROFILE .env astro.config.mjs src/content.config.t
25
25
 
26
26
  ## Frontmatter schemas
27
27
 
28
+ **Universal field (v4.9.0):** every profile accepts an optional `slug:` string that overrides the URL. A file `99-appendix.mdx` with `slug: appendix` is served at `/chapters/appendix/` — Astro's glob loader maps frontmatter `slug` → `entry.id`, and cross-references (`<XRef>`, via `build-labels`) resolve to the same path. Omit it and the URL falls back to the filename. Use it to keep numbered filenames for ordering while publishing clean URLs.
29
+
28
30
  ### Academic profile (`src/content.config.ts:academicChapterSchema`)
29
31
 
30
32
  ```yaml
@@ -34,6 +36,7 @@ part: foundations # required: foundations|ssm-core|beyond-ssm|integration
34
36
  title: "..." # string, required
35
37
  status: implemented # required: implemented|chapter_only|prose_only|code_only|reading_only|scaffolded|planned
36
38
  # optional:
39
+ slug: ch01-introduction # clean URL override; else filename → /chapters/<slug>/
37
40
  roadmap_lines: [10, 42] # [start, end] line refs into roadmap.md
38
41
  code_path: experiments/jax/week01/foo.py
39
42
  tests_path: experiments/jax/week01/test_foo.py
@@ -54,7 +57,7 @@ volatility: architectural-pattern # required: stable-principle|architectural-pa
54
57
  tools_compared: [claude-code] # required, ≥1 of: claude-code|gemini-cli|codex-cli|cross-tool
55
58
  last_verified: 2026-05-18 # date, required
56
59
  sources: [] # array of source-manifest keys
57
- # optional: description, draft, updated
60
+ # optional: slug (clean URL override), description, draft, updated
58
61
  ---
59
62
  ```
60
63
 
@@ -1,28 +1,31 @@
1
1
  ---
2
- /**
3
- * XRef — resolves a `\cref{label}` LaTeX reference to a hyperlink.
4
- *
5
- * Reads src/data/labels.json built by scripts/build-labels.mjs
6
- * (Phase 2.6). For each known id, the map provides:
7
- * { href: "/chapters/week04#thm-w4-stability", display: "Theorem 4.2" }
8
- *
9
- * Runtime: renders `<a href>` for known ids; renders an inline `[?id]`
10
- * placeholder for unknown ids so the Astro dev server stays running while
11
- * chapters are being authored or labels are being added.
12
- *
13
- * CI: `book-scaffold validate` (Phase 2.6, shipped) catches unknown ids
14
- * and **fails the build with a non-zero exit code** before unresolved
15
- * placeholders can reach production. The placeholder is a dev-ergonomic
16
- * affordance, not a soft-degradation path on the deploy critical line.
17
- *
18
- * Bootstrapping note: when porting a book chapter-by-chapter, early
19
- * chapters that reference yet-to-be-ported targets need either plain-prose
20
- * substitutes or a temporarily-commented `{/* <XRef …/> */}` until the
21
- * target chapter (and its `id="…"` attributes) exists.
22
- *
23
- * Usage:
24
- * By <XRef id="thm:w4:stability" />, the discretized eigenvalues
25
- */
2
+ // XRef — resolves a `\cref{label}` LaTeX reference to a hyperlink.
3
+ //
4
+ // Reads src/data/labels.json built by scripts/build-labels.mjs
5
+ // (Phase 2.6). For each known id, the map provides:
6
+ // { href: "/chapters/week04#thm-w4-stability", display: "Theorem 4.2" }
7
+ //
8
+ // Runtime: renders `<a href>` for known ids; renders an inline `[?id]`
9
+ // placeholder for unknown ids so the Astro dev server stays running while
10
+ // chapters are being authored or labels are being added.
11
+ //
12
+ // CI: `book-scaffold validate` (Phase 2.6, shipped) catches unknown ids
13
+ // and **fails the build with a non-zero exit code** before unresolved
14
+ // placeholders can reach production. The placeholder is a dev-ergonomic
15
+ // affordance, not a soft-degradation path on the deploy critical line.
16
+ //
17
+ // Bootstrapping note: when porting a book chapter-by-chapter, early
18
+ // chapters that reference yet-to-be-ported targets need either plain-prose
19
+ // substitutes or a temporarily-commented `{/* <XRef …/> */}` until the
20
+ // target chapter (and its `id="…"` attributes) exists. MDX uses JSX-style
21
+ // expression comments the HTML `<` + bang + `--` form is NOT valid MDX.
22
+ //
23
+ // NB: these are `//` line comments, not a `/** */` block, on purpose — the
24
+ // literal `*/` in the example above would otherwise close a block comment
25
+ // early and break esbuild on every MDX import (the v4.9.0 fix).
26
+ //
27
+ // Usage:
28
+ // By <XRef id="thm:w4:stability" />, the discretized eigenvalues …
26
29
  type LabelEntry = { href: string; display: string };
27
30
 
28
31
  // Resolve labels.json from the consumer's project root (Vite resolves `/`
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, h as ChaptersRenderer, o as Style } from './types-DTQ2l6B6.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, i as CourseNotesChapter, F as FreshnessAffordance, j as FrontmatterRouteConfig, M as MinimalChapter, P as PartKey, k as PartialRouteToggles, l as ProfileDefinition, m as Provenance, R as ResearchPortfolioChapter, n as RouteToggles, S as StatusBadge, p as StyleInput, T as ToolsChapter, V as VolatilityBadge, q as academicChapterSchema, r as academicParts, s as changeKinds, t as changelogSchema, u as chapterStatus, v as citationBackstops, w as composeStyles, x as courseNotesChapterSchema, y as defineProfile, z as defineStyle, D as minimalChapterSchema, E as normalizeFrontmatterConfig, G as patternCategories, H as patternsSchema, I as provenanceObject, J as provenanceSchema, K as researchPortfolioChapterSchema, L as resolvePreset, N as resolveProfile, O as sourceTiers, Q as sourceTiersResearch, U as sourcesSchema, W as toolSlugs, X as toolsChapterSchema } from './types-DTQ2l6B6.js';
2
+ import { c as BookConfigOptions, f as BookScaffoldIntegrationOptions, Y as volatilityLevels, h as ChaptersRenderer, o as Style } from './types-CULHImU4.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, i as CourseNotesChapter, F as FreshnessAffordance, j as FrontmatterRouteConfig, M as MinimalChapter, P as PartKey, k as PartialRouteToggles, l as ProfileDefinition, m as Provenance, R as ResearchPortfolioChapter, n as RouteToggles, S as StatusBadge, p as StyleInput, T as ToolsChapter, V as VolatilityBadge, q as academicChapterSchema, r as academicParts, s as changeKinds, t as changelogSchema, u as chapterStatus, v as citationBackstops, w as composeStyles, x as courseNotesChapterSchema, y as defineProfile, z as defineStyle, D as minimalChapterSchema, E as normalizeFrontmatterConfig, G as patternCategories, H as patternsSchema, I as provenanceObject, J as provenanceSchema, K as researchPortfolioChapterSchema, L as resolvePreset, N as resolveProfile, O as sourceTiers, Q as sourceTiersResearch, U as sourcesSchema, W as toolSlugs, X as toolsChapterSchema } from './types-CULHImU4.js';
4
4
  import 'astro/zod';
5
5
 
6
6
  /**
package/dist/index.mjs CHANGED
@@ -184,6 +184,8 @@ var academicChapterSchema = z.object({
184
184
  week: z.number().int().min(1).max(99),
185
185
  part: z.enum(academicParts),
186
186
  title: z.string().min(1),
187
+ slug: z.string().optional(),
188
+ // v4.9.0: explicit URL slug override (else filename → entry.id)
187
189
  status: z.enum(chapterStatus),
188
190
  roadmap_lines: z.tuple([z.number().int(), z.number().int()]).optional(),
189
191
  code_path: z.string().optional(),
@@ -203,6 +205,8 @@ var academicChapterSchema = z.object({
203
205
  });
204
206
  var toolsChapterSchema = z.object({
205
207
  title: z.string().min(1),
208
+ slug: z.string().optional(),
209
+ // v4.9.0: explicit URL slug override (else filename → entry.id)
206
210
  part: z.number().int().min(0).max(10),
207
211
  chapter: z.number().int().min(0).max(99),
208
212
  volatility: z.enum(volatilityLevels),
@@ -226,6 +230,8 @@ var sourceTiersResearch = ["T1", "T2", "T3", "T4"];
226
230
  var courseNotesChapterSchema = z.object({
227
231
  // Identity
228
232
  title: z.string().min(1),
233
+ slug: z.string().optional(),
234
+ // v4.9.0: explicit URL slug override (else filename → entry.id)
229
235
  chapter: z.number().int().min(0).max(99),
230
236
  part: z.number().int().min(0).max(20).default(1),
231
237
  description: z.string().optional(),
package/dist/schemas.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { defineCollection } from 'astro:content';
2
- import { g as BookSchemasOptions } from './types-DTQ2l6B6.js';
2
+ import { g as BookSchemasOptions } from './types-CULHImU4.js';
3
3
  import 'astro';
4
4
  import 'astro/zod';
5
5
 
package/dist/schemas.mjs CHANGED
@@ -68,6 +68,8 @@ var academicChapterSchema = z.object({
68
68
  week: z.number().int().min(1).max(99),
69
69
  part: z.enum(academicParts),
70
70
  title: z.string().min(1),
71
+ slug: z.string().optional(),
72
+ // v4.9.0: explicit URL slug override (else filename → entry.id)
71
73
  status: z.enum(chapterStatus),
72
74
  roadmap_lines: z.tuple([z.number().int(), z.number().int()]).optional(),
73
75
  code_path: z.string().optional(),
@@ -87,6 +89,8 @@ var academicChapterSchema = z.object({
87
89
  });
88
90
  var toolsChapterSchema = z.object({
89
91
  title: z.string().min(1),
92
+ slug: z.string().optional(),
93
+ // v4.9.0: explicit URL slug override (else filename → entry.id)
90
94
  part: z.number().int().min(0).max(10),
91
95
  chapter: z.number().int().min(0).max(99),
92
96
  volatility: z.enum(volatilityLevels),
@@ -110,6 +114,8 @@ var sourceTiersResearch = ["T1", "T2", "T3", "T4"];
110
114
  var courseNotesChapterSchema = z.object({
111
115
  // Identity
112
116
  title: z.string().min(1),
117
+ slug: z.string().optional(),
118
+ // v4.9.0: explicit URL slug override (else filename → entry.id)
113
119
  chapter: z.number().int().min(0).max(99),
114
120
  part: z.number().int().min(0).max(20).default(1),
115
121
  description: z.string().optional(),
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@brandon_m_behring/book-scaffold-astro",
3
3
  "description": "Astro 6 + MDX toolkit for long-form technical books. Profile-aware (academic / tools / minimal); ships Tufte typography, KaTeX, BibTeX citations, Pagefind, Cloudflare Workers deploy. See PACKAGE_DESIGN.md for the API contract.",
4
- "version": "4.8.0",
4
+ "version": "4.9.0",
5
5
  "type": "module",
6
6
  "license": "MIT",
7
7
  "author": "Brandon Behring",
@@ -15,10 +15,11 @@
15
15
  * - tools profile: `chapter` field (number).
16
16
  * - academic profile: `week` field (number).
17
17
  *
18
- * Slug used for the href = filename minus `.mdx`. The href shape mirrors
19
- * the consumer's pages router: `/chapters/<slug>#<id>`. Academic books
20
- * using `[...slug].astro` get the same shape since Astro slugifies
21
- * filenames identically.
18
+ * Slug used for the href: the chapter's frontmatter `slug:` if set,
19
+ * else filename minus `.mdx`. The href shape mirrors the consumer's pages
20
+ * router: `/chapters/<slug>#<id>`. Academic books using `[...slug].astro`
21
+ * get the same shape since Astro slugifies filenames identically when no
22
+ * frontmatter override is present.
22
23
  *
23
24
  * Optional override:
24
25
  * <Theorem id="…" label="Custom display" />
@@ -178,7 +179,9 @@ async function main() {
178
179
  const source = await readFile(file, 'utf8');
179
180
  const fm = parseFrontmatter(source);
180
181
  const chapterNum = chapterNumberOf(fm);
181
- const slug = basename(file).replace(/\.mdx?$/, '');
182
+ const slug = (typeof fm.slug === 'string' && fm.slug.length > 0)
183
+ ? fm.slug
184
+ : basename(file).replace(/\.mdx?$/, '');
182
185
 
183
186
  // Per-chapter counters reset for each file.
184
187
  const counters = {};
package/src/schemas.ts CHANGED
@@ -116,6 +116,7 @@ export const academicChapterSchema = z.object({
116
116
  week: z.number().int().min(1).max(99),
117
117
  part: z.enum(academicParts),
118
118
  title: z.string().min(1),
119
+ slug: z.string().optional(), // v4.9.0: explicit URL slug override (else filename → entry.id)
119
120
  status: z.enum(chapterStatus),
120
121
  roadmap_lines: z.tuple([z.number().int(), z.number().int()]).optional(),
121
122
  code_path: z.string().optional(),
@@ -136,6 +137,7 @@ export const academicChapterSchema = z.object({
136
137
 
137
138
  export const toolsChapterSchema = z.object({
138
139
  title: z.string().min(1),
140
+ slug: z.string().optional(), // v4.9.0: explicit URL slug override (else filename → entry.id)
139
141
  part: z.number().int().min(0).max(10),
140
142
  chapter: z.number().int().min(0).max(99),
141
143
  volatility: z.enum(volatilityLevels),
@@ -178,6 +180,7 @@ export const sourceTiersResearch = ['T1', 'T2', 'T3', 'T4'] as const;
178
180
  export const courseNotesChapterSchema = z.object({
179
181
  // Identity
180
182
  title: z.string().min(1),
183
+ slug: z.string().optional(), // v4.9.0: explicit URL slug override (else filename → entry.id)
181
184
  chapter: z.number().int().min(0).max(99),
182
185
  part: z.number().int().min(0).max(20).default(1),
183
186
  description: z.string().optional(),