@brandon_m_behring/book-scaffold-astro 4.26.0 → 4.26.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { AstroUserConfig, AstroIntegration, MarkdownHeading } from 'astro';
2
- import { c as BookConfigOptions, f as BookScaffoldIntegrationOptions, h as ChaptersRenderer, l as Style } from './types-BO5qyDqT.js';
3
- export { B as BOOK_PRESETS, a as BOOK_PROFILES, b as BookConfigError, d as BookPreset, e as BookProfile, g as BookSchemasOptions, C as ChapterFor, F as FreshnessAffordance, i as FrontmatterRouteConfig, P as PartKey, j as PartialRouteToggles, k as ProfileDefinition, R as RouteToggles, S as StatusBadge, m as StyleInput, V as VolatilityBadge, n as composeStyles, o as defineProfile, p as defineStyle, q as normalizeFrontmatterConfig, r as resolvePreset, s as resolveProfile } from './types-BO5qyDqT.js';
2
+ import { c as BookConfigOptions, f as BookScaffoldIntegrationOptions, h as ChaptersRenderer, m as Style } from './types-CWXP1S4b.js';
3
+ export { B as BOOK_PRESETS, a as BOOK_PROFILES, b as BookConfigError, d as BookPreset, e as BookProfile, g as BookSchemasOptions, C as ChapterFor, F as FreshnessAffordance, i as FrontmatterRouteConfig, P as PartKey, j as PartialRouteToggles, k as ProfileDefinition, R as ReleaseStatusConfig, l as RouteToggles, S as StatusBadge, n as StyleInput, V as VolatilityBadge, o as composeStyles, p as defineProfile, q as defineStyle, r as normalizeFrontmatterConfig, s as resolvePreset, t as resolveProfile } from './types-CWXP1S4b.js';
4
4
  import { E as volatilityLevels, c as academicParts, Q as Question } from './schemas-CKipJ5Ie.js';
5
5
  export { A as AcademicChapter, B as BloomLevel, C as CourseNotesChapter, G as GlossaryTerm, M as MinimalChapter, P as Provenance, a as QuestionType, R as ResearchPortfolioChapter, T as ToolsChapter, b as academicChapterSchema, d as bloomLevels, e as changeKinds, f as changelogSchema, g as chapterStatus, h as citationBackstops, i as courseNotesChapterSchema, j as glossarySchema, l as layoutModes, m as minimalChapterSchema, p as patternCategories, k as patternsSchema, n as provenanceObject, o as provenanceSchema, q as questionDifficulties, r as questionSchema, s as questionTypes, t as refineQuestion, u as refinedQuestionSchema, v as researchPortfolioChapterSchema, w as sourceTiers, x as sourceTiersResearch, y as sourcesSchema, z as toolSlugs, D as toolsChapterSchema } from './schemas-CKipJ5Ie.js';
6
6
  export { KIND_LABEL, ResolvedTheoremLabel, THEOREM_KINDS, TheoremKind, TheoremLabelProps, resolveTheoremNumber, theoremLabel } from './lib/theorem-label.js';
@@ -144,18 +144,31 @@ declare const academicChaptersRenderer: ChaptersRenderer;
144
144
 
145
145
  /**
146
146
  * src/profiles/renderers/fallback-chapters.ts — ChaptersRenderer used by
147
- * profiles that don't ship a dedicated renderer (minimal, course-notes,
148
- * research-portfolio). Dispatches by field presence — exactly the v3.5.2
147
+ * profiles that don't ship a dedicated renderer (minimal, course-notes).
148
+ * Dispatches by field presence — exactly the v3.5.2
149
149
  * logic that lived inline in pages/chapters.astro before #35.
150
150
  *
151
151
  * Safety net for shapes we haven't designed for explicitly. If a consumer
152
- * opts a course-notes or research-portfolio book into `routes.chapters: true`,
152
+ * opts a course-notes book into `routes.chapters: true`,
153
153
  * the fallback renders reasonably without crashing. Custom output for those
154
154
  * profiles is a v4+ extension point (consumer-overridable renderer).
155
155
  */
156
156
 
157
157
  declare const fallbackChaptersRenderer: ChaptersRenderer;
158
158
 
159
+ /**
160
+ * Research-portfolio /chapters renderer.
161
+ *
162
+ * The portfolio schema intentionally permits numeric Parts through 20. The
163
+ * generic fallback renderer inherits the tools profile's historical
164
+ * `part >= 6` appendix convention, which mislabels a portfolio's sixth Part
165
+ * as "Appendices" and its chapters as Appendix a/b/.... Keep the fallback's
166
+ * field-shape dispatch for every other affordance, but make numeric Parts
167
+ * ordinary numbered Parts throughout the portfolio schema's full range.
168
+ */
169
+
170
+ declare const researchPortfolioChaptersRenderer: ChaptersRenderer;
171
+
159
172
  /**
160
173
  * src/lib/academic-parts.ts — single source of truth for academic-profile
161
174
  * part labels (#95).
@@ -598,4 +611,4 @@ type TipsConfigInput = Omit<TipsConfig, typeof TipsConfigBrand | '__tipsConfigVe
598
611
  */
599
612
  declare function defineTips(opts: TipsConfigInput): TipsConfig;
600
613
 
601
- export { ACADEMIC_PART_NAMES, BRANDON_PORTFOLIO_DEFAULT, BUILTIN_STYLES, BookConfigOptions, BookScaffoldIntegrationOptions, type ChapterLike, ChaptersRenderer, DEFAULT_GITHUB_BRANCH, type Freshness, type FreshnessStatus, type PartReviewGroup, type PartReviewSelection, Question, type ReviewChapter, type ReviewExercise, Style, type TipsConfig, type TipsConfigInput, UNKNOWN_PART_ORDINAL, type VisibleHeading, type VolatilityLevel, academicChaptersRenderer, academicPartHeading, academicPartName, academicPartOrdinal, academicParts, academicStyle, apparatusHref, assertEnumProp, assertKnownDomain, bookOf, bookScaffoldIntegration, buildGithubUrl, chapterHref, chapterLabel, chapterSortKey, courseNotesStyle, defineBookConfig, defineMdxComponents, defineTips, deriveObjectiveMap, distinctChaptersSorted, fallbackChaptersRenderer, freshnessLabel, getFreshness, groupByChapter, groupByDomain, isCurrentChapter, minimalStyle, originUrlFromGitConfig, parseRepoSlug, pickActive, researchPortfolioStyle, resolveBookHref, resolveGithubRepo, selectPartExercises, slugOf, sortQuestions, tocHeadings, toolsChaptersRenderer, toolsStyle, volatilityLevels };
614
+ export { ACADEMIC_PART_NAMES, BRANDON_PORTFOLIO_DEFAULT, BUILTIN_STYLES, BookConfigOptions, BookScaffoldIntegrationOptions, type ChapterLike, ChaptersRenderer, DEFAULT_GITHUB_BRANCH, type Freshness, type FreshnessStatus, type PartReviewGroup, type PartReviewSelection, Question, type ReviewChapter, type ReviewExercise, Style, type TipsConfig, type TipsConfigInput, UNKNOWN_PART_ORDINAL, type VisibleHeading, type VolatilityLevel, academicChaptersRenderer, academicPartHeading, academicPartName, academicPartOrdinal, academicParts, academicStyle, apparatusHref, assertEnumProp, assertKnownDomain, bookOf, bookScaffoldIntegration, buildGithubUrl, chapterHref, chapterLabel, chapterSortKey, courseNotesStyle, defineBookConfig, defineMdxComponents, defineTips, deriveObjectiveMap, distinctChaptersSorted, fallbackChaptersRenderer, freshnessLabel, getFreshness, groupByChapter, groupByDomain, isCurrentChapter, minimalStyle, originUrlFromGitConfig, parseRepoSlug, pickActive, researchPortfolioChaptersRenderer, researchPortfolioStyle, resolveBookHref, resolveGithubRepo, selectPartExercises, slugOf, sortQuestions, tocHeadings, toolsChaptersRenderer, toolsStyle, volatilityLevels };
package/dist/index.mjs CHANGED
@@ -851,6 +851,17 @@ var courseNotesProfile = defineProfile({
851
851
  sitemapFilter: (page) => !page.includes("/print/")
852
852
  });
853
853
 
854
+ // src/profiles/renderers/research-portfolio-chapters.ts
855
+ var researchPortfolioChaptersRenderer = {
856
+ ...fallbackChaptersRenderer,
857
+ formatPartLabel(part) {
858
+ return typeof part === "number" ? `Part ${part}` : fallbackChaptersRenderer.formatPartLabel(part);
859
+ },
860
+ isAppendix(_part) {
861
+ return false;
862
+ }
863
+ };
864
+
854
865
  // src/profiles/research-portfolio.ts
855
866
  var researchPortfolioProfile = defineProfile({
856
867
  name: "research-portfolio",
@@ -883,8 +894,9 @@ var researchPortfolioProfile = defineProfile({
883
894
  styles: ["tokens.css", "layout.css", "callouts.css", "chapter.css", "typography.css", "print.css", "section-map.css"],
884
895
  katex: true,
885
896
  // math is common in research content
886
- // v3.7.0 (#35): portfolio schema is a union of academic + tools shapes fallback renderer dispatches per chapter via field presence
887
- chaptersRenderer: fallbackChaptersRenderer
897
+ // Portfolio schema is a union of academic + tools shapes, but unlike the
898
+ // tools profile its numeric Parts remain numbered through the schema max 20.
899
+ chaptersRenderer: researchPortfolioChaptersRenderer
888
900
  });
889
901
 
890
902
  // src/profiles/index.ts
@@ -971,6 +983,9 @@ function composeStyles(styles) {
971
983
  if (style.preset !== void 0) merged.preset = style.preset;
972
984
  if (style.site !== void 0) merged.site = style.site;
973
985
  if (style.deploy !== void 0) merged.deploy = style.deploy;
986
+ if (style.releaseStatus !== void 0) {
987
+ merged.releaseStatus = style.releaseStatus;
988
+ }
974
989
  if (style.mdxComponentsModule !== void 0) {
975
990
  merged.mdxComponentsModule = style.mdxComponentsModule;
976
991
  }
@@ -1199,6 +1214,7 @@ function bookScaffoldIntegration(opts) {
1199
1214
  // v4.5.0: landing-page data, propagated via virtual module to /index.astro.
1200
1215
  title,
1201
1216
  subtitle,
1217
+ releaseStatus,
1202
1218
  description,
1203
1219
  portfolio,
1204
1220
  // v4.6.0: book-level author + SEO config, propagated through the
@@ -1268,6 +1284,7 @@ function bookScaffoldIntegration(opts) {
1268
1284
  makeBookConfigVitePlugin({
1269
1285
  title: title ?? null,
1270
1286
  subtitle: subtitle ?? null,
1287
+ releaseStatus: releaseStatus ?? null,
1271
1288
  description: description ?? null,
1272
1289
  portfolio: portfolio ?? false,
1273
1290
  enabledRoutes: enabledRouteNames,
@@ -1364,6 +1381,7 @@ async function defineBookConfig(opts) {
1364
1381
  const mdxComponentsModule = opts.mdxComponentsModule ?? composed.mdxComponentsModule;
1365
1382
  const composedMarkdown = composed.markdown ?? {};
1366
1383
  const userMarkdown = opts.markdown ?? {};
1384
+ const resolvedReleaseStatus = opts.releaseStatus !== void 0 ? opts.releaseStatus : composed.releaseStatus;
1367
1385
  const wantsKatex = PROFILES[profile]?.katex === true;
1368
1386
  const remarkPlugins = [];
1369
1387
  const rehypePlugins = [];
@@ -1413,6 +1431,8 @@ async function defineBookConfig(opts) {
1413
1431
  title: opts.title,
1414
1432
  // v4.23.0 (#135): sidebar brand subtitle.
1415
1433
  subtitle: opts.subtitle,
1434
+ // v4.26.2 (#149; style inheritance + opt-out fixed in v4.26.3).
1435
+ releaseStatus: resolvedReleaseStatus === false ? void 0 : resolvedReleaseStatus,
1416
1436
  description: opts.description,
1417
1437
  portfolio: resolvedPortfolio,
1418
1438
  // v4.6.0: book-level author + SEO config (ogImage, twitterHandle),
@@ -1470,6 +1490,8 @@ async function defineBookConfig(opts) {
1470
1490
  // v4.5.0: strip new landing-related opts so they don't leak into AstroUserConfig.
1471
1491
  title: _title,
1472
1492
  subtitle: _subtitle,
1493
+ // v4.26.2 (#149): strip the release-state banner config.
1494
+ releaseStatus: _releaseStatus,
1473
1495
  description: _description,
1474
1496
  portfolio: _portfolio,
1475
1497
  // v4.6.0: strip new book-level SEO opts (author + seo block).
@@ -1500,6 +1522,7 @@ async function defineBookConfig(opts) {
1500
1522
  void _katexMacros;
1501
1523
  void _title;
1502
1524
  void _subtitle;
1525
+ void _releaseStatus;
1503
1526
  void _description;
1504
1527
  void _portfolio;
1505
1528
  void _author;
@@ -1953,6 +1976,7 @@ export {
1953
1976
  refineQuestion,
1954
1977
  refinedQuestionSchema,
1955
1978
  researchPortfolioChapterSchema,
1979
+ researchPortfolioChaptersRenderer,
1956
1980
  researchPortfolioStyle,
1957
1981
  resolveBookHref,
1958
1982
  resolveGithubRepo,
package/dist/schemas.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { defineCollection } from 'astro:content';
2
- import { g as BookSchemasOptions } from './types-BO5qyDqT.js';
2
+ import { g as BookSchemasOptions } from './types-CWXP1S4b.js';
3
3
  import 'astro';
4
4
  import './schemas-CKipJ5Ie.js';
5
5
  import 'astro/zod';
package/dist/schemas.mjs CHANGED
@@ -729,6 +729,17 @@ var courseNotesProfile = defineProfile({
729
729
  sitemapFilter: (page) => !page.includes("/print/")
730
730
  });
731
731
 
732
+ // src/profiles/renderers/research-portfolio-chapters.ts
733
+ var researchPortfolioChaptersRenderer = {
734
+ ...fallbackChaptersRenderer,
735
+ formatPartLabel(part) {
736
+ return typeof part === "number" ? `Part ${part}` : fallbackChaptersRenderer.formatPartLabel(part);
737
+ },
738
+ isAppendix(_part) {
739
+ return false;
740
+ }
741
+ };
742
+
732
743
  // src/profiles/research-portfolio.ts
733
744
  var researchPortfolioProfile = defineProfile({
734
745
  name: "research-portfolio",
@@ -761,8 +772,9 @@ var researchPortfolioProfile = defineProfile({
761
772
  styles: ["tokens.css", "layout.css", "callouts.css", "chapter.css", "typography.css", "print.css", "section-map.css"],
762
773
  katex: true,
763
774
  // math is common in research content
764
- // v3.7.0 (#35): portfolio schema is a union of academic + tools shapes fallback renderer dispatches per chapter via field presence
765
- chaptersRenderer: fallbackChaptersRenderer
775
+ // Portfolio schema is a union of academic + tools shapes, but unlike the
776
+ // tools profile its numeric Parts remain numbered through the schema max 20.
777
+ chaptersRenderer: researchPortfolioChaptersRenderer
766
778
  });
767
779
 
768
780
  // src/profiles/index.ts
@@ -379,6 +379,12 @@ interface Style {
379
379
  * - `'pages'`: Cloudflare Pages (default for research-portfolio/course-notes)
380
380
  * Closes #50. */
381
381
  readonly deploy?: 'pages' | 'workers';
382
+ /**
383
+ * v4.26.2 (#149; style inheritance fixed in v4.26.3): release-state
384
+ * banner. Shallow override (last defined wins); `false` suppresses a
385
+ * banner inherited from an earlier style.
386
+ */
387
+ readonly releaseStatus?: ReleaseStatusConfig | false;
382
388
  /**
383
389
  * Scoped consumer-side metadata. Ignored by the toolkit; survives composition
384
390
  * as per-key spread (last wins per key). Use this for workflow data that
@@ -439,7 +445,8 @@ declare function defineStyle(opts: StyleInput): Style;
439
445
  * - Top-level `defineBookConfig` fields beat any style (handled in config.ts)
440
446
  *
441
447
  * Per-key merge strategy:
442
- * - `preset`, `site`, `deploy`, `mdxComponentsModule`, `name` → shallow override (last wins)
448
+ * - `preset`, `site`, `deploy`, `mdxComponentsModule`, `name`, `releaseStatus`
449
+ * → shallow override (last defined wins; `releaseStatus: false` suppresses)
443
450
  * - `routes` → per-route spread (each route key independently overridable)
444
451
  * - `katexMacros` → per-macro spread (each macro key independently overridable)
445
452
  * - `extra` → per-key spread (consumer metadata accumulates across the chain)
@@ -475,6 +482,17 @@ declare function normalizeFrontmatterConfig(v: FrontmatterRouteConfig | undefine
475
482
 
476
483
  type BookPreset = BookProfile;
477
484
  declare const BOOK_PRESETS: readonly ("academic" | "tools" | "minimal" | "course-notes" | "research-portfolio")[];
485
+ /**
486
+ * v4.26.2 (#149): book-level release state rendered by
487
+ * `<PreReleaseBanner>` across every page.
488
+ *
489
+ * Style inheritance and explicit suppression were fixed in v4.26.3.
490
+ */
491
+ interface ReleaseStatusConfig {
492
+ state: 'alpha' | 'beta' | 'rc' | 'locked';
493
+ dismissAt?: string;
494
+ message?: string;
495
+ }
478
496
  /**
479
497
  * Options for `defineBookConfig`. See PACKAGE_DESIGN.md §4.
480
498
  *
@@ -600,6 +618,13 @@ interface BookConfigOptions {
600
618
  * hardcoded both strings, so every consumer shipped the placeholder).
601
619
  */
602
620
  subtitle?: string;
621
+ /**
622
+ * v4.26.2 (#149; style inheritance fixed in v4.26.3): book-level release
623
+ * state. When set, Base.astro renders the existing <PreReleaseBanner>
624
+ * site-wide (top of <body>) with these props. Omit to inherit from the
625
+ * composed style chain; set `false` to suppress an inherited banner.
626
+ */
627
+ releaseStatus?: ReleaseStatusConfig | false;
603
628
  /**
604
629
  * v4.5.0: Book description. Read by the auto-injected `/` landing page (lead paragraph + <meta description>).
605
630
  * Optional; landing renders no description paragraph if unset.
@@ -752,6 +777,9 @@ interface BookScaffoldIntegrationOptions {
752
777
  /** v4.23.0 (#135): sidebar brand subtitle, propagated via the book-config
753
778
  * virtual module to Sidebar.astro. */
754
779
  subtitle?: string;
780
+ /** v4.26.2 (#149; style inheritance fixed in v4.26.3): resolved
781
+ * release-state banner propagated via the book-config virtual module. */
782
+ releaseStatus?: ReleaseStatusConfig;
755
783
  /** v4.5.0: book description, propagated to `/` landing via vite.define. */
756
784
  description?: string;
757
785
  /**
@@ -827,4 +855,4 @@ declare function resolvePreset(explicitPreset?: BookPreset, explicitProfile?: Bo
827
855
  */
828
856
  declare function resolveProfile(explicit?: BookProfile): BookProfile;
829
857
 
830
- export { BOOK_PRESETS as B, type ChapterFor as C, type FreshnessAffordance as F, type PartKey as P, type RouteToggles as R, type StatusBadge as S, type VolatilityBadge as V, BOOK_PROFILES as a, BookConfigError as b, type BookConfigOptions as c, type BookPreset as d, type BookProfile as e, type BookScaffoldIntegrationOptions as f, type BookSchemasOptions as g, type ChaptersRenderer as h, type FrontmatterRouteConfig as i, type PartialRouteToggles as j, type ProfileDefinition as k, type Style as l, type StyleInput as m, composeStyles as n, defineProfile as o, defineStyle as p, normalizeFrontmatterConfig as q, resolvePreset as r, resolveProfile as s };
858
+ export { BOOK_PRESETS as B, type ChapterFor as C, type FreshnessAffordance as F, type PartKey as P, type ReleaseStatusConfig as R, type StatusBadge as S, type VolatilityBadge as V, BOOK_PROFILES as a, BookConfigError as b, type BookConfigOptions as c, type BookPreset as d, type BookProfile as e, type BookScaffoldIntegrationOptions as f, type BookSchemasOptions as g, type ChaptersRenderer as h, type FrontmatterRouteConfig as i, type PartialRouteToggles as j, type ProfileDefinition as k, type RouteToggles as l, type Style as m, type StyleInput as n, composeStyles as o, defineProfile as p, defineStyle as q, normalizeFrontmatterConfig as r, resolvePreset as s, resolveProfile as t };
@@ -59,6 +59,9 @@ import '../styles/print.css';
59
59
  import VersionSelector from '@brandon_m_behring/book-scaffold-astro/components/VersionSelector';
60
60
  import ToolFilter from '@brandon_m_behring/book-scaffold-astro/components/ToolFilter';
61
61
  import Sidebar from '../components/Sidebar.astro';
62
+ // v4.26.2 (#149; style inheritance fixed in v4.26.3): site-wide
63
+ // release-state banner, driven by defineBookConfig.
64
+ import PreReleaseBanner from '../components/PreReleaseBanner.astro';
62
65
  // v4.26.0 (#80): the mobile/tablet drawer reuses the same book-scoped nav source.
63
66
  import NavContent from '../components/NavContent.astro';
64
67
  // v4.6.0: SEO meta tags read book-level identity (title fallback,
@@ -165,6 +168,16 @@ const baseUrl = (import.meta.env.BASE_URL ?? '/').replace(/\/*$/, '/');
165
168
  <slot name="head" />
166
169
  </head>
167
170
  <body>
171
+ {/* v4.26.2 (#149; style inheritance fixed in v4.26.3): book-level release banner — first element in <body> so
172
+ every page (chapters, apparatus, landing) declares its state. Renders
173
+ only when defineBookConfig({ releaseStatus }) is set. */}
174
+ {bookConfig.releaseStatus && (
175
+ <PreReleaseBanner
176
+ state={bookConfig.releaseStatus.state}
177
+ dismissAt={bookConfig.releaseStatus.dismissAt}
178
+ message={bookConfig.releaseStatus.message}
179
+ />
180
+ )}
168
181
  <div class="chrome-buttons">
169
182
  {/* v4.26.0 (#80): hamburger → mobile/tablet nav drawer. <a href="#nav-drawer">
170
183
  so it opens via :target with no JS; the controller below enhances it. */}
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.26.0",
4
+ "version": "4.26.3",
5
5
  "type": "module",
6
6
  "license": "MIT",
7
7
  "author": "Brandon Behring",
@@ -122,7 +122,8 @@ Different fields have different merge semantics. Documented:
122
122
 
123
123
  | Field | Strategy |
124
124
  |---|---|
125
- | `name`, `preset`, `site`, `deploy`, `mdxComponentsModule` | Shallow override (last wins) |
125
+ | `name`, `preset`, `site`, `deploy`, `mdxComponentsModule` | Shallow override (last defined wins) |
126
+ | `releaseStatus` | Shallow override (last defined object replaces the whole earlier object); `false` suppresses an inherited banner |
126
127
  | `routes` | Per-route spread (each route key independently overridable) |
127
128
  | `routes.frontmatter` | Per-route spread; later value (boolean OR object) wholly replaces earlier |
128
129
  | `katexMacros` | Object spread (per-macro override) |
@@ -27,7 +27,7 @@
27
27
  * for migration from the v3 `preset:` shorthand.
28
28
  */
29
29
  import type { AstroIntegration, AstroUserConfig } from 'astro';
30
- import type { BookPreset, RouteToggles } from '../types.js';
30
+ import type { BookPreset, ReleaseStatusConfig, RouteToggles } from '../types.js';
31
31
 
32
32
  // ===== Branded nominal type =====
33
33
 
@@ -123,6 +123,12 @@ export interface Style {
123
123
  * - `'pages'`: Cloudflare Pages (default for research-portfolio/course-notes)
124
124
  * Closes #50. */
125
125
  readonly deploy?: 'pages' | 'workers';
126
+ /**
127
+ * v4.26.2 (#149; style inheritance fixed in v4.26.3): release-state
128
+ * banner. Shallow override (last defined wins); `false` suppresses a
129
+ * banner inherited from an earlier style.
130
+ */
131
+ readonly releaseStatus?: ReleaseStatusConfig | false;
126
132
 
127
133
  /**
128
134
  * Scoped consumer-side metadata. Ignored by the toolkit; survives composition
@@ -193,7 +199,8 @@ export function defineStyle(opts: StyleInput): Style {
193
199
  * - Top-level `defineBookConfig` fields beat any style (handled in config.ts)
194
200
  *
195
201
  * Per-key merge strategy:
196
- * - `preset`, `site`, `deploy`, `mdxComponentsModule`, `name` → shallow override (last wins)
202
+ * - `preset`, `site`, `deploy`, `mdxComponentsModule`, `name`, `releaseStatus`
203
+ * → shallow override (last defined wins; `releaseStatus: false` suppresses)
197
204
  * - `routes` → per-route spread (each route key independently overridable)
198
205
  * - `katexMacros` → per-macro spread (each macro key independently overridable)
199
206
  * - `extra` → per-key spread (consumer metadata accumulates across the chain)
@@ -216,6 +223,9 @@ export function composeStyles(styles: readonly Style[]): Style {
216
223
  if (style.preset !== undefined) merged.preset = style.preset;
217
224
  if (style.site !== undefined) merged.site = style.site;
218
225
  if (style.deploy !== undefined) merged.deploy = style.deploy;
226
+ if (style.releaseStatus !== undefined) {
227
+ merged.releaseStatus = style.releaseStatus;
228
+ }
219
229
  if (style.mdxComponentsModule !== undefined) {
220
230
  merged.mdxComponentsModule = style.mdxComponentsModule;
221
231
  }
@@ -1,11 +1,11 @@
1
1
  /**
2
2
  * src/profiles/renderers/fallback-chapters.ts — ChaptersRenderer used by
3
- * profiles that don't ship a dedicated renderer (minimal, course-notes,
4
- * research-portfolio). Dispatches by field presence — exactly the v3.5.2
3
+ * profiles that don't ship a dedicated renderer (minimal, course-notes).
4
+ * Dispatches by field presence — exactly the v3.5.2
5
5
  * logic that lived inline in pages/chapters.astro before #35.
6
6
  *
7
7
  * Safety net for shapes we haven't designed for explicitly. If a consumer
8
- * opts a course-notes or research-portfolio book into `routes.chapters: true`,
8
+ * opts a course-notes book into `routes.chapters: true`,
9
9
  * the fallback renders reasonably without crashing. Custom output for those
10
10
  * profiles is a v4+ extension point (consumer-overridable renderer).
11
11
  */
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Research-portfolio /chapters renderer.
3
+ *
4
+ * The portfolio schema intentionally permits numeric Parts through 20. The
5
+ * generic fallback renderer inherits the tools profile's historical
6
+ * `part >= 6` appendix convention, which mislabels a portfolio's sixth Part
7
+ * as "Appendices" and its chapters as Appendix a/b/.... Keep the fallback's
8
+ * field-shape dispatch for every other affordance, but make numeric Parts
9
+ * ordinary numbered Parts throughout the portfolio schema's full range.
10
+ */
11
+ import type { ChaptersRenderer } from '../../lib/chapters-renderer.js';
12
+ import { fallbackChaptersRenderer } from './fallback-chapters.js';
13
+
14
+ export const researchPortfolioChaptersRenderer: ChaptersRenderer = {
15
+ ...fallbackChaptersRenderer,
16
+
17
+ formatPartLabel(part) {
18
+ return typeof part === 'number'
19
+ ? `Part ${part}`
20
+ : fallbackChaptersRenderer.formatPartLabel(part);
21
+ },
22
+
23
+ isAppendix(_part) {
24
+ return false;
25
+ },
26
+ };
@@ -22,7 +22,7 @@
22
22
  */
23
23
  import { defineProfile } from '../profile-kit.js';
24
24
  import { researchPortfolioChapterSchema } from '../schemas.js';
25
- import { fallbackChaptersRenderer } from './renderers/fallback-chapters.js';
25
+ import { researchPortfolioChaptersRenderer } from './renderers/research-portfolio-chapters.js';
26
26
 
27
27
  export type { ResearchPortfolioChapter } from '../schemas.js';
28
28
 
@@ -46,6 +46,7 @@ export const researchPortfolioProfile = defineProfile({
46
46
  },
47
47
  styles: ['tokens.css', 'layout.css', 'callouts.css', 'chapter.css', 'typography.css', 'print.css', 'section-map.css'],
48
48
  katex: true, // math is common in research content
49
- // v3.7.0 (#35): portfolio schema is a union of academic + tools shapes fallback renderer dispatches per chapter via field presence
50
- chaptersRenderer: fallbackChaptersRenderer,
49
+ // Portfolio schema is a union of academic + tools shapes, but unlike the
50
+ // tools profile its numeric Parts remain numbered through the schema max 20.
51
+ chaptersRenderer: researchPortfolioChaptersRenderer,
51
52
  });
@@ -244,4 +244,15 @@
244
244
  overflow-x: auto;
245
245
  /* a little room so the horizontal scrollbar doesn't clip the equation's descenders */
246
246
  padding-block-end: 0.25rem;
247
+ /* Edge scroll-shadow when a wide equation scrolls (v4.25.3; docs/responsive-reading.md).
248
+ * Same Lea Verou layered technique as code blocks, page-bg covers. */
249
+ background-image:
250
+ linear-gradient(to right, var(--color-bg) 30%, rgba(0, 0, 0, 0)),
251
+ linear-gradient(to right, rgba(0, 0, 0, 0), var(--color-bg) 70%),
252
+ radial-gradient(farthest-side at 0 50%, rgba(0, 0, 0, 0.12), rgba(0, 0, 0, 0)),
253
+ radial-gradient(farthest-side at 100% 50%, rgba(0, 0, 0, 0.12), rgba(0, 0, 0, 0));
254
+ background-position: 0 0, 100% 0, 0 0, 100% 0;
255
+ background-repeat: no-repeat;
256
+ background-size: 30px 100%, 30px 100%, 12px 100%, 12px 100%;
257
+ background-attachment: local, local, scroll, scroll;
247
258
  }
package/styles/tokens.css CHANGED
@@ -108,6 +108,7 @@
108
108
  */
109
109
  --measure-main: 60ch; /* default: main+gutter fits beside the sidebar at ≥64rem (v4.26.0, #80) */
110
110
  --measure-side: 20ch; /* right-margin sidenote / section-map column */
111
+ --measure-code: 48rem; /* code-block break-out width: fits 80-char lines comfortably (≈88 mono chars @14px before padding); docs/responsive-reading.md */
111
112
  --breakpoint-narrow: 48rem; /* ~768px — mobile fold (sidenotes inline) */
112
113
  --breakpoint-wide: 90rem; /* ~1440px — wide-desktop tier */
113
114
  --breakpoint-ultrawide: 120rem; /* ~1920px — ultrawide tier */
@@ -89,6 +89,43 @@ pre code {
89
89
  font-size: var(--text-sm);
90
90
  }
91
91
 
92
+ /* ===== Responsive reading (v4.25.3) — docs/responsive-reading.md =====
93
+ * Code blocks break out past the prose text measure to fit ~80-char lines,
94
+ * capped at --measure-code and centered within .prose. CONTAINER-bounded, not
95
+ * viewport-bounded: the width comes from the .prose column itself, so a left
96
+ * sidebar offset + the scrollbar gutter can never push the block into horizontal
97
+ * PAGE scroll. (The earlier `width: min(100vw - …)` overflowed ~8px at the
98
+ * 1024px sidebar boundary — #171 review finding A1, guarded by
99
+ * gallery/tests/fixtures/layout-overflow.spec.ts.) `.wide`/`.column-page` keep
100
+ * their full-bleed behavior (excluded here). */
101
+ .prose > pre:not(.wide):not(.column-page) {
102
+ max-width: var(--measure-code);
103
+ margin-inline: auto;
104
+ /* Lea Verou layered scroll-shadow: bg-colored covers move with content
105
+ * (attachment:local); shadow layers are fixed → shown only when scrollable. */
106
+ background-image:
107
+ linear-gradient(to right, var(--color-code-bg) 30%, rgba(0, 0, 0, 0)),
108
+ linear-gradient(to right, rgba(0, 0, 0, 0), var(--color-code-bg) 70%),
109
+ radial-gradient(farthest-side at 0 50%, rgba(0, 0, 0, 0.18), rgba(0, 0, 0, 0)),
110
+ radial-gradient(farthest-side at 100% 50%, rgba(0, 0, 0, 0.18), rgba(0, 0, 0, 0));
111
+ background-position: 0 0, 100% 0, 0 0, 100% 0;
112
+ background-repeat: no-repeat;
113
+ background-size: 40px 100%, 40px 100%, 14px 100%, 14px 100%;
114
+ background-attachment: local, local, scroll, scroll;
115
+ }
116
+ /* Phone (≤40rem): shrink code a touch so more fits before scrolling. Tighter
117
+ * than the 48rem mobile/table breakpoint below — phones benefit from the smaller
118
+ * glyphs; tablets (40–48rem) keep the full size. */
119
+ @media (max-width: 40rem) {
120
+ pre code { font-size: 0.75rem; }
121
+ }
122
+ /* Mobile: scroll a wide table within its own block instead of overflowing the
123
+ * page. Sticky thead intentionally NOT used — it can't stick inside a
124
+ * horizontal-scroll wrapper (CSS limitation; see docs/responsive-reading.md). */
125
+ @media (max-width: 48rem) {
126
+ .prose table { display: block; max-width: 100%; overflow-x: auto; }
127
+ }
128
+
92
129
  /* ===== Links ===== */
93
130
  a {
94
131
  color: var(--color-link);