@shoalrun/cli 0.1.0-alpha.19 → 0.1.0-beta.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.
Files changed (151) hide show
  1. package/CHANGELOG.md +13 -0
  2. package/README.md +15 -5
  3. package/agent-docs/.well-known/shoalrun/navigation/project-updates-50f26fe22670.txt +5 -3
  4. package/agent-docs/SNAPSHOT.json +5 -5
  5. package/agent-docs/about.md +3 -3
  6. package/agent-docs/author/agent-overview.md +2 -2
  7. package/agent-docs/author/components/code-blocks.md +46 -1
  8. package/agent-docs/author/frontmatter-reference.md +1 -0
  9. package/agent-docs/author/frontmatter-reference.sections/categorize-connect-and-present-the-page.md +1 -0
  10. package/agent-docs/author/workspace.md +2 -2
  11. package/agent-docs/author/writing-style.md +9 -3
  12. package/agent-docs/author/writing-style.sections/adapt-this-example.md +1 -1
  13. package/agent-docs/author/writing-style.sections/choose-components-by-reader-need.md +2 -2
  14. package/agent-docs/author/writing-style.sections/evidence-and-uncertainty.md +1 -1
  15. package/agent-docs/author/writing-style.sections/example-audience-and-scope.md +1 -1
  16. package/agent-docs/author/writing-style.sections/feature-availability.md +1 -1
  17. package/agent-docs/author/writing-style.sections/ingredients-recipes-and-cookbooks.md +1 -1
  18. package/agent-docs/author/writing-style.sections/page-purpose-and-structure.md +2 -2
  19. package/agent-docs/author/writing-style.sections/procedures-and-examples.md +1 -1
  20. package/agent-docs/author/writing-style.sections/related-reading.md +1 -1
  21. package/agent-docs/author/writing-style.sections/release-notes.md +30 -0
  22. package/agent-docs/author/writing-style.sections/review-a-draft.md +1 -1
  23. package/agent-docs/author/writing-style.sections/reviewed-writing-examples.md +1 -1
  24. package/agent-docs/author/writing-style.sections/voice-and-explanations.md +1 -1
  25. package/agent-docs/changelog/2026-10-16-navigation-layouts.md +1 -1
  26. package/agent-docs/changelog/pdf-custom-languages.md +48 -0
  27. package/agent-docs/changelog.md +9 -1
  28. package/agent-docs/config/examples.md +3 -3
  29. package/agent-docs/config/examples.sections/feature-showcase.md +3 -3
  30. package/agent-docs/config/examples.sections/minimal-site.md +2 -2
  31. package/agent-docs/config/examples.sections/multilingual-help-center.md +2 -2
  32. package/agent-docs/config/examples.sections/parcel-api-documentation.md +2 -2
  33. package/agent-docs/config/examples.sections/related-reading.md +2 -2
  34. package/agent-docs/config/examples.sections/shared-company-preset.md +2 -2
  35. package/agent-docs/config/outputs.md +33 -12
  36. package/agent-docs/config/outputs.sections/exports.md +3 -3
  37. package/agent-docs/config/outputs.sections/metadata.md +3 -3
  38. package/agent-docs/config/outputs.sections/pdf-output.md +51 -0
  39. package/agent-docs/config/outputs.sections/related-reading.md +3 -3
  40. package/agent-docs/configuration.md +2 -2
  41. package/agent-docs/configuration.sections/build-several-sites-from-shared-content.md +1 -1
  42. package/agent-docs/configuration.sections/change-inherited-lists-deliberately.md +1 -1
  43. package/agent-docs/configuration.sections/extract-a-section-with-include.md +1 -1
  44. package/agent-docs/configuration.sections/inspect-and-validate-changes.md +1 -1
  45. package/agent-docs/configuration.sections/related-reading.md +1 -1
  46. package/agent-docs/configuration.sections/separate-config-inheritance-from-page-metadata.md +1 -1
  47. package/agent-docs/configuration.sections/share-defaults-through-extends.md +1 -1
  48. package/agent-docs/configuration.sections/start-with-one-working-file.md +2 -2
  49. package/agent-docs/configuration.sections/understand-the-three-stages.md +1 -1
  50. package/agent-docs/configuration.sections/understand-where-paths-point.md +1 -1
  51. package/agent-docs/contribute/agent-skills.md +3 -1
  52. package/agent-docs/documents.json +510 -251
  53. package/agent-docs/features.md +4 -1
  54. package/agent-docs/index.md +2 -2
  55. package/agent-docs/provenance.json +6 -6
  56. package/agent-docs/publish/content-export.md +2 -0
  57. package/agent-docs/publish/content-export.sections/assets-downloads-and-notices.md +2 -0
  58. package/agent-docs/publish/content-export.sections/check-the-result.md +2 -0
  59. package/agent-docs/publish/content-export.sections/inspect-the-manifest.md +2 -0
  60. package/agent-docs/publish/content-export.sections/prerequisites.md +2 -0
  61. package/agent-docs/publish/content-export.sections/related-reading.md +2 -0
  62. package/agent-docs/publish/content-export.sections/review-exported-api-material.md +2 -0
  63. package/agent-docs/publish/content-export.sections/try-the-help-drawer-example.md +2 -0
  64. package/agent-docs/publish/content-export.sections/what-the-representations-retain.md +2 -0
  65. package/agent-docs/publish/pdf.md +184 -0
  66. package/agent-docs/publish/pdf.sections/apply-print-styling.md +65 -0
  67. package/agent-docs/publish/pdf.sections/check-the-result.md +33 -0
  68. package/agent-docs/publish/pdf.sections/export-a-page-or-publication.md +61 -0
  69. package/agent-docs/publish/pdf.sections/html-markdown-and-agents.md +31 -0
  70. package/agent-docs/publish/pdf.sections/offer-browser-page-printing.md +49 -0
  71. package/agent-docs/publish/pdf.sections/offer-direct-downloads-through-a-node-server.md +46 -0
  72. package/agent-docs/publish/pdf.sections/prerequisites.md +37 -0
  73. package/agent-docs/publish/pdf.sections/publish-an-optional-manual-download.md +39 -0
  74. package/agent-docs/publish.md +2 -0
  75. package/agent-docs/reference/agent-skills.md +7 -3
  76. package/agent-docs/search-index.json +1 -1
  77. package/agent-docs/start.md +4 -4
  78. package/agent-docs/start.sections/add-the-page-to-navigation.md +2 -2
  79. package/agent-docs/start.sections/build-and-check-both-outputs.md +2 -2
  80. package/agent-docs/start.sections/build-this-site.md +2 -2
  81. package/agent-docs/start.sections/check-writing-style.md +2 -2
  82. package/agent-docs/start.sections/create-a-page.md +2 -2
  83. package/agent-docs/start.sections/make-it-yours.md +2 -2
  84. package/agent-docs/start.sections/migrating-from-github-packages.md +2 -2
  85. package/agent-docs/start.sections/prerequisites.md +2 -2
  86. package/agent-docs/start.sections/related-reading.md +2 -2
  87. package/agent-docs/start.sections/start-your-own-project.md +4 -4
  88. package/agent-docs/status.md +7 -5
  89. package/agent-docs/what-is-shoalrun.md +2 -2
  90. package/assets/copy-markdown.js +1 -1
  91. package/assets/print-view.js +165 -0
  92. package/assets/print.js +63 -0
  93. package/dist/artifact-bundle.d.ts +1 -0
  94. package/dist/artifact-bundle.js +2 -0
  95. package/dist/authoring-snapshot.js +1 -1
  96. package/dist/browser-print-assets.d.ts +9 -0
  97. package/dist/browser-print-assets.js +45 -0
  98. package/dist/compiler.d.ts +4 -2
  99. package/dist/compiler.js +29 -4
  100. package/dist/content-export-command.d.ts +77 -1
  101. package/dist/content-export-command.js +5 -2
  102. package/dist/content-export-config.d.ts +4 -1
  103. package/dist/content-export-config.js +10 -5
  104. package/dist/content-export-render.d.ts +9 -3
  105. package/dist/content-export-render.js +11 -7
  106. package/dist/content-export.d.ts +126 -0
  107. package/dist/content-export.js +267 -124
  108. package/dist/content-source.d.ts +1 -0
  109. package/dist/frontmatter.js +5 -2
  110. package/dist/highlight-languages.d.ts +9 -0
  111. package/dist/highlight-languages.js +52 -0
  112. package/dist/highlight.d.ts +4 -1
  113. package/dist/highlight.js +20 -5
  114. package/dist/output-commit.d.ts +1 -1
  115. package/dist/output-commit.js +4 -2
  116. package/dist/page-renderer.js +6 -1
  117. package/dist/pdf-button.d.ts +22 -0
  118. package/dist/pdf-button.js +55 -0
  119. package/dist/pdf-config.d.ts +28 -0
  120. package/dist/pdf-config.js +43 -0
  121. package/dist/pdf-http.d.ts +36 -0
  122. package/dist/pdf-http.js +132 -0
  123. package/dist/pdf-project.d.ts +6 -0
  124. package/dist/pdf-project.js +31 -0
  125. package/dist/pdf-renderer.d.ts +45 -0
  126. package/dist/pdf-renderer.js +208 -0
  127. package/dist/pdf-site.d.ts +37 -0
  128. package/dist/pdf-site.js +65 -0
  129. package/dist/pdf-stylesheet.d.ts +1 -0
  130. package/dist/pdf-stylesheet.js +57 -0
  131. package/dist/representation-tree.js +1 -1
  132. package/dist/server.d.ts +2 -0
  133. package/dist/server.js +35 -8
  134. package/dist/vercel.js +6 -0
  135. package/examples/custom-language/README.md +32 -0
  136. package/examples/custom-language/content/start.mdx +28 -0
  137. package/examples/custom-language/docs.config.yaml +18 -0
  138. package/examples/custom-language/grammars/custompy.tmLanguage.json +7 -0
  139. package/examples/pdf/README.md +27 -0
  140. package/examples/pdf/content/operations.mdx +26 -0
  141. package/examples/pdf/content/start.mdx +28 -0
  142. package/examples/pdf/docs.config.yaml +29 -0
  143. package/examples/pdf/print.css +9 -0
  144. package/package.json +4 -2
  145. package/skills/shoalrun-author/SKILL.md +2 -0
  146. package/skills/shoalrun-author/references/pdf-export.md +15 -0
  147. package/skills/shoalrun-brand/SKILL.md +2 -0
  148. package/skills/shoalrun-pdf/SKILL.md +49 -0
  149. package/skills/shoalrun-pdf/assets/print.css +49 -0
  150. package/skills/shoalrun-pdf/references/print-design.md +75 -0
  151. package/styles/site.css +3 -1
@@ -16,11 +16,11 @@ Navigation section: [Docs](https://www.shoalrun.com/.well-known/shoalrun/navigat
16
16
 
17
17
  Breadcrumbs: [Docs](https://www.shoalrun.com/what-is-shoalrun.md) > Get started > Getting started
18
18
 
19
- **Updated: 2026-11-05.**
19
+ **Updated: 2026-11-06.**
20
20
 
21
21
  This quickstart takes you from installing Shoalrun to your own working documentation site. You'll scaffold a page from a template, complete its draft, put it in the sidebar, and read its HTML and Markdown output.
22
22
 
23
- > **Alpha:** Pin an exact version and review release notes before upgrading. Configuration and output formats may change between alpha releases.
23
+ > **Beta:** Pin an exact version and review release notes before upgrading. Configuration and output formats may change between beta releases.
24
24
 
25
25
  ## Prerequisites
26
26
 
@@ -30,13 +30,13 @@ The public npm package needs no account or token.
30
30
 
31
31
  ## Start your own project
32
32
 
33
- Install an exact version of the alpha and initialize the minimal starter:
33
+ Install an exact version of the beta and initialize the minimal starter:
34
34
 
35
35
  ```sh
36
36
  mkdir my-docs
37
37
  cd my-docs
38
38
  npm init -y
39
- npm install --save-exact @shoalrun/cli@0.1.0-alpha.19
39
+ npm install --save-exact @shoalrun/cli@0.1.0-beta.1
40
40
  npx --no-install shoalrun init .
41
41
  ```
42
42
 
@@ -16,7 +16,7 @@ Navigation section: [Docs](https://www.shoalrun.com/.well-known/shoalrun/navigat
16
16
 
17
17
  Breadcrumbs: [Docs](https://www.shoalrun.com/what-is-shoalrun.md) > Get started > Getting started
18
18
 
19
- **Updated: 2026-11-05.**
19
+ **Updated: 2026-11-06.**
20
20
 
21
21
  Parent page: [Full Markdown page](/start.md)
22
22
 
@@ -28,7 +28,7 @@ Parent page: [Full Markdown page](/start.md)
28
28
 
29
29
  This quickstart takes you from installing Shoalrun to your own working documentation site. You'll scaffold a page from a template, complete its draft, put it in the sidebar, and read its HTML and Markdown output.
30
30
 
31
- > **Alpha:** Pin an exact version and review release notes before upgrading. Configuration and output formats may change between alpha releases.
31
+ > **Beta:** Pin an exact version and review release notes before upgrading. Configuration and output formats may change between beta releases.
32
32
 
33
33
  ## Add the page to navigation
34
34
 
@@ -16,7 +16,7 @@ Navigation section: [Docs](https://www.shoalrun.com/.well-known/shoalrun/navigat
16
16
 
17
17
  Breadcrumbs: [Docs](https://www.shoalrun.com/what-is-shoalrun.md) > Get started > Getting started
18
18
 
19
- **Updated: 2026-11-05.**
19
+ **Updated: 2026-11-06.**
20
20
 
21
21
  Parent page: [Full Markdown page](/start.md)
22
22
 
@@ -28,7 +28,7 @@ Parent page: [Full Markdown page](/start.md)
28
28
 
29
29
  This quickstart takes you from installing Shoalrun to your own working documentation site. You'll scaffold a page from a template, complete its draft, put it in the sidebar, and read its HTML and Markdown output.
30
30
 
31
- > **Alpha:** Pin an exact version and review release notes before upgrading. Configuration and output formats may change between alpha releases.
31
+ > **Beta:** Pin an exact version and review release notes before upgrading. Configuration and output formats may change between beta releases.
32
32
 
33
33
  ## Build and check both outputs
34
34
 
@@ -16,7 +16,7 @@ Navigation section: [Docs](https://www.shoalrun.com/.well-known/shoalrun/navigat
16
16
 
17
17
  Breadcrumbs: [Docs](https://www.shoalrun.com/what-is-shoalrun.md) > Get started > Getting started
18
18
 
19
- **Updated: 2026-11-05.**
19
+ **Updated: 2026-11-06.**
20
20
 
21
21
  Parent page: [Full Markdown page](/start.md)
22
22
 
@@ -28,7 +28,7 @@ Parent page: [Full Markdown page](/start.md)
28
28
 
29
29
  This quickstart takes you from installing Shoalrun to your own working documentation site. You'll scaffold a page from a template, complete its draft, put it in the sidebar, and read its HTML and Markdown output.
30
30
 
31
- > **Alpha:** Pin an exact version and review release notes before upgrading. Configuration and output formats may change between alpha releases.
31
+ > **Beta:** Pin an exact version and review release notes before upgrading. Configuration and output formats may change between beta releases.
32
32
 
33
33
  ## Build this site
34
34
 
@@ -16,7 +16,7 @@ Navigation section: [Docs](https://www.shoalrun.com/.well-known/shoalrun/navigat
16
16
 
17
17
  Breadcrumbs: [Docs](https://www.shoalrun.com/what-is-shoalrun.md) > Get started > Getting started
18
18
 
19
- **Updated: 2026-11-05.**
19
+ **Updated: 2026-11-06.**
20
20
 
21
21
  Parent page: [Full Markdown page](/start.md)
22
22
 
@@ -28,7 +28,7 @@ Parent page: [Full Markdown page](/start.md)
28
28
 
29
29
  This quickstart takes you from installing Shoalrun to your own working documentation site. You'll scaffold a page from a template, complete its draft, put it in the sidebar, and read its HTML and Markdown output.
30
30
 
31
- > **Alpha:** Pin an exact version and review release notes before upgrading. Configuration and output formats may change between alpha releases.
31
+ > **Beta:** Pin an exact version and review release notes before upgrading. Configuration and output formats may change between beta releases.
32
32
 
33
33
  ## Check writing style
34
34
 
@@ -16,7 +16,7 @@ Navigation section: [Docs](https://www.shoalrun.com/.well-known/shoalrun/navigat
16
16
 
17
17
  Breadcrumbs: [Docs](https://www.shoalrun.com/what-is-shoalrun.md) > Get started > Getting started
18
18
 
19
- **Updated: 2026-11-05.**
19
+ **Updated: 2026-11-06.**
20
20
 
21
21
  Parent page: [Full Markdown page](/start.md)
22
22
 
@@ -28,7 +28,7 @@ Parent page: [Full Markdown page](/start.md)
28
28
 
29
29
  This quickstart takes you from installing Shoalrun to your own working documentation site. You'll scaffold a page from a template, complete its draft, put it in the sidebar, and read its HTML and Markdown output.
30
30
 
31
- > **Alpha:** Pin an exact version and review release notes before upgrading. Configuration and output formats may change between alpha releases.
31
+ > **Beta:** Pin an exact version and review release notes before upgrading. Configuration and output formats may change between beta releases.
32
32
 
33
33
  ## Create a page
34
34
 
@@ -16,7 +16,7 @@ Navigation section: [Docs](https://www.shoalrun.com/.well-known/shoalrun/navigat
16
16
 
17
17
  Breadcrumbs: [Docs](https://www.shoalrun.com/what-is-shoalrun.md) > Get started > Getting started
18
18
 
19
- **Updated: 2026-11-05.**
19
+ **Updated: 2026-11-06.**
20
20
 
21
21
  Parent page: [Full Markdown page](/start.md)
22
22
 
@@ -28,7 +28,7 @@ Parent page: [Full Markdown page](/start.md)
28
28
 
29
29
  This quickstart takes you from installing Shoalrun to your own working documentation site. You'll scaffold a page from a template, complete its draft, put it in the sidebar, and read its HTML and Markdown output.
30
30
 
31
- > **Alpha:** Pin an exact version and review release notes before upgrading. Configuration and output formats may change between alpha releases.
31
+ > **Beta:** Pin an exact version and review release notes before upgrading. Configuration and output formats may change between beta releases.
32
32
 
33
33
  ## Make it yours
34
34
 
@@ -16,7 +16,7 @@ Navigation section: [Docs](https://www.shoalrun.com/.well-known/shoalrun/navigat
16
16
 
17
17
  Breadcrumbs: [Docs](https://www.shoalrun.com/what-is-shoalrun.md) > Get started > Getting started
18
18
 
19
- **Updated: 2026-11-05.**
19
+ **Updated: 2026-11-06.**
20
20
 
21
21
  Parent page: [Full Markdown page](/start.md)
22
22
 
@@ -28,7 +28,7 @@ Parent page: [Full Markdown page](/start.md)
28
28
 
29
29
  This quickstart takes you from installing Shoalrun to your own working documentation site. You'll scaffold a page from a template, complete its draft, put it in the sidebar, and read its HTML and Markdown output.
30
30
 
31
- > **Alpha:** Pin an exact version and review release notes before upgrading. Configuration and output formats may change between alpha releases.
31
+ > **Beta:** Pin an exact version and review release notes before upgrading. Configuration and output formats may change between beta releases.
32
32
 
33
33
  ## Migrating from GitHub Packages
34
34
 
@@ -16,7 +16,7 @@ Navigation section: [Docs](https://www.shoalrun.com/.well-known/shoalrun/navigat
16
16
 
17
17
  Breadcrumbs: [Docs](https://www.shoalrun.com/what-is-shoalrun.md) > Get started > Getting started
18
18
 
19
- **Updated: 2026-11-05.**
19
+ **Updated: 2026-11-06.**
20
20
 
21
21
  Parent page: [Full Markdown page](/start.md)
22
22
 
@@ -28,7 +28,7 @@ Parent page: [Full Markdown page](/start.md)
28
28
 
29
29
  This quickstart takes you from installing Shoalrun to your own working documentation site. You'll scaffold a page from a template, complete its draft, put it in the sidebar, and read its HTML and Markdown output.
30
30
 
31
- > **Alpha:** Pin an exact version and review release notes before upgrading. Configuration and output formats may change between alpha releases.
31
+ > **Beta:** Pin an exact version and review release notes before upgrading. Configuration and output formats may change between beta releases.
32
32
 
33
33
  ## Prerequisites
34
34
 
@@ -16,13 +16,13 @@ Navigation section: [Docs](https://www.shoalrun.com/.well-known/shoalrun/navigat
16
16
 
17
17
  Breadcrumbs: [Docs](https://www.shoalrun.com/what-is-shoalrun.md) > Get started > Getting started
18
18
 
19
- **Updated: 2026-11-05.**
19
+ **Updated: 2026-11-06.**
20
20
 
21
21
  Parent page: [Full Markdown page](/start.md)
22
22
 
23
23
  This quickstart takes you from installing Shoalrun to your own working documentation site. You'll scaffold a page from a template, complete its draft, put it in the sidebar, and read its HTML and Markdown output.
24
24
 
25
- > **Alpha:** Pin an exact version and review release notes before upgrading. Configuration and output formats may change between alpha releases.
25
+ > **Beta:** Pin an exact version and review release notes before upgrading. Configuration and output formats may change between beta releases.
26
26
 
27
27
  ## Related reading
28
28
 
@@ -16,7 +16,7 @@ Navigation section: [Docs](https://www.shoalrun.com/.well-known/shoalrun/navigat
16
16
 
17
17
  Breadcrumbs: [Docs](https://www.shoalrun.com/what-is-shoalrun.md) > Get started > Getting started
18
18
 
19
- **Updated: 2026-11-05.**
19
+ **Updated: 2026-11-06.**
20
20
 
21
21
  Parent page: [Full Markdown page](/start.md)
22
22
 
@@ -28,17 +28,17 @@ Parent page: [Full Markdown page](/start.md)
28
28
 
29
29
  This quickstart takes you from installing Shoalrun to your own working documentation site. You'll scaffold a page from a template, complete its draft, put it in the sidebar, and read its HTML and Markdown output.
30
30
 
31
- > **Alpha:** Pin an exact version and review release notes before upgrading. Configuration and output formats may change between alpha releases.
31
+ > **Beta:** Pin an exact version and review release notes before upgrading. Configuration and output formats may change between beta releases.
32
32
 
33
33
  ## Start your own project
34
34
 
35
- Install an exact version of the alpha and initialize the minimal starter:
35
+ Install an exact version of the beta and initialize the minimal starter:
36
36
 
37
37
  ```sh
38
38
  mkdir my-docs
39
39
  cd my-docs
40
40
  npm init -y
41
- npm install --save-exact @shoalrun/cli@0.1.0-alpha.19
41
+ npm install --save-exact @shoalrun/cli@0.1.0-beta.1
42
42
  npx --no-install shoalrun init .
43
43
  ```
44
44
 
@@ -16,11 +16,11 @@ Navigation section: [Project](https://www.shoalrun.com/.well-known/shoalrun/navi
16
16
 
17
17
  Breadcrumbs: [Project](https://www.shoalrun.com/status.md) > Project > Project status
18
18
 
19
- **Updated: 2026-11-05.**
19
+ **Updated: 2026-11-06.**
20
20
 
21
- Shoalrun is an alpha for publishing documentation for people and agents from the same source. This site is built with it. Install the public `@shoalrun/cli` npm package through [Getting started](https://www.shoalrun.com/start.md). No account or package token is needed.
21
+ Shoalrun is in beta for publishing documentation for people and agents from the same source. This site is built with it. Install the public `@shoalrun/cli` npm package through [Getting started](https://www.shoalrun.com/start.md). No account or package token is needed.
22
22
 
23
- The [features overview](https://www.shoalrun.com/features.md) summarizes the platform on one page. The capabilities below include `0.1.0-alpha.18`.
23
+ The [features overview](https://www.shoalrun.com/features.md) summarizes the platform on one page. The capabilities below include `0.1.0-beta.1`.
24
24
 
25
25
  ## What works today
26
26
 
@@ -30,6 +30,8 @@ The [features overview](https://www.shoalrun.com/features.md) summarizes the pla
30
30
 
31
31
  - **Rich documentation pages.** Built-in [components](https://www.shoalrun.com/author/components.md) include tabs, callouts, tables, code walkthroughs, steps, timelines, and API embeds, with defined Markdown output. [Diagrams](https://www.shoalrun.com/author/components/diagrams.md) render D2 and Mermaid syntax as SVG during the build; Pele renders the Mermaid diagrams. Markdown retains the diagram source and explanation.
32
32
 
33
+ - **Code highlighting and PDFs.** Register [custom code languages](https://www.shoalrun.com/author/components/code-blocks.md) and offer [styled page printing or PDF exports](https://www.shoalrun.com/publish/pdf.md), including optional publication manuals and the `shoalrun-pdf` agent skill.
34
+
33
35
  - **Reader controls.** [File trees](https://www.shoalrun.com/author/components/file-tree.md) have expandable folders and keyboard navigation. [Collapsible groups](https://www.shoalrun.com/author/components/collapsible.md) join related disclosures without gaps. Authors can synchronize related [tabs](https://www.shoalrun.com/author/components/tabs.md) and add captions or expanded viewing to [images](https://www.shoalrun.com/author/components/images.md).
34
36
 
35
37
  - **Configurable sites.** Themes, palettes, layouts, headers, and footers shape the presentation. [Navigation tabs](https://www.shoalrun.com/author/navigation.md) organize larger sites into sections. Explicit [versions and languages](https://www.shoalrun.com/publish/publications.md) publish separate page variants, while reader views tailor navigation and marked content to different audiences.
@@ -60,7 +62,7 @@ These are intended additions, without a promised release date.
60
62
 
61
63
  ## Current limits
62
64
 
63
- - **Alpha stability.** Configuration, extension points, and output formats can still change. There is no stable release or compatibility promise yet.
65
+ - **Beta stability.** Configuration, extension points, and output formats can still change. There is no stable release or compatibility promise yet.
64
66
  - **Component extensibility.** [Trusted custom components](https://www.shoalrun.com/author/components/custom.md) lower into shared content, with a portable authoring skill. Arbitrary executable MDX remains unsupported. Project CSS, browser modules, and configured build extensions are trusted project code.
65
67
  - **API coverage.** OpenAPI and JSON Schema support is partial; check the [support reference](https://www.shoalrun.com/api-docs/support.md) against your source. Request examples are copyable, but Shoalrun doesn't provide a live API explorer that sends requests.
66
68
  - **Private content.** Reader views don't restrict access to published files. Shoalrun has no built-in reader authentication, and its HTTP MCP endpoint currently serves one selected public publication. Host-level authorization is a separate responsibility.
@@ -82,4 +84,4 @@ Automatic skill generation and general agent orchestration are [deliberate non-g
82
84
 
83
85
  - [Changelog](https://www.shoalrun.com/changelog.md) — Check implemented capabilities and the remaining release work.
84
86
 
85
- - [Navigation, page layouts, and browser analytics](https://www.shoalrun.com/changelog/2026-10-16-navigation-layouts.md) — Check the alpha's capabilities and current limits.
87
+ - [Navigation, page layouts, and browser analytics](https://www.shoalrun.com/changelog/2026-10-16-navigation-layouts.md) — Check the current capabilities and limits.
@@ -17,7 +17,7 @@ Navigation section: [Docs](https://www.shoalrun.com/.well-known/shoalrun/navigat
17
17
 
18
18
  Breadcrumbs: [Docs](https://www.shoalrun.com/what-is-shoalrun.md) > Get started > What is Shoalrun?
19
19
 
20
- **Updated: 2026-11-05.**
20
+ **Updated: 2026-11-06.**
21
21
 
22
22
  Shoalrun is a documentation compiler for product and API docs. It reads project configuration, Markdown and supported MDX pages, and optional local OpenAPI files. From those sources, it builds a site for people and readable Markdown pages for agents. The normal build is deterministic: it makes no model calls or network requests.
23
23
 
@@ -43,7 +43,7 @@ Optional ingredients, recipes, and cookbooks extend the same idea to tasks. An i
43
43
 
44
44
  ## Where it stands
45
45
 
46
- The public documentation site shows output built by Shoalrun. Install the alpha CLI from npm using [Getting started](https://www.shoalrun.com/start.md). The [project status](https://www.shoalrun.com/status.md) lists implemented capabilities and current limits.
46
+ The public documentation site shows output built by Shoalrun. Install the beta CLI from npm using [Getting started](https://www.shoalrun.com/start.md). The [project status](https://www.shoalrun.com/status.md) lists implemented capabilities and current limits.
47
47
 
48
48
  ## Related reading
49
49
 
@@ -1,6 +1,6 @@
1
1
  const button = document.querySelector('[data-copy-markdown]');
2
2
  const status = document.querySelector('[data-copy-markdown-status]');
3
- const link = document.querySelector('.page-formats a');
3
+ const link = document.querySelector('[data-markdown-link]');
4
4
  if (button && status && link) {
5
5
  button.hidden = false;
6
6
  button.addEventListener('click', async () => {
@@ -0,0 +1,165 @@
1
+ const htmlTags = new Set('a abbr article b blockquote br caption code col colgroup dd del div dl dt em figcaption figure header footer h1 h2 h3 h4 h5 h6 hr i img kbd li ol p pre progress s section small span strong sub sup table tbody td th thead time tr ul'.split(' '));
2
+ const mathTags = new Set('math semantics annotation mrow mi mn mo mtext mspace ms mfrac msqrt mroot mstyle merror mpadded mphantom mfenced menclose msub msup msubsup munder mover munderover mmultiscripts mprescripts none mtable mtr mtd mlabeledtr'.split(' '));
3
+ const svgTags = new Set('svg g defs marker path rect circle ellipse line polyline polygon text tspan title desc linearGradient radialGradient stop clipPath pattern symbol use'.toLowerCase().split(' '));
4
+ const attributes = new Set('id class alt title colspan rowspan start reversed value max datetime scope align role aria-label aria-labelledby aria-describedby aria-hidden data-document-type'.split(' '));
5
+ const mathAttributes = new Set('xmlns display mathvariant encoding stretchy fence separator accent accentunder form lspace rspace minsize maxsize symmetric largeop movablelimits scriptlevel displaystyle linethickness notation columnalign columnspacing rowalign rowspacing columnspan rowspan width height depth voffset'.split(' '));
6
+ const svgAttributes = new Set('viewbox width height x y x1 y1 x2 y2 cx cy r rx ry d points fill stroke stroke-width stroke-linecap stroke-linejoin opacity fill-opacity stroke-opacity transform preserveaspectratio marker-start marker-mid marker-end offset stop-color stop-opacity clip-path fill-rule clip-rule'.split(' '));
7
+ const blocked = new Set('script style iframe object embed link base meta form select textarea audio video canvas noscript'.split(' '));
8
+ const browserOnly = new Set('doc-tablist reader-filter-slot reader-filter-controls reader-outside-view heading-anchor code-copy copy-status file-tree-controls doc-image-expand hero-actions example-picker api-view-control code-samples-bar'.split(' '));
9
+ const referrerPolicies = new Set('no-referrer no-referrer-when-downgrade origin origin-when-cross-origin same-origin strict-origin strict-origin-when-cross-origin unsafe-url'.split(' '));
10
+
11
+ export function printUrl(value, base, image = false) {
12
+ try {
13
+ const url = new URL(value, base);
14
+ if (url.username || url.password || !['http:', 'https:', ...(image ? [] : ['mailto:', 'tel:'])].includes(url.protocol)) return undefined;
15
+ const current = new URL(base);
16
+ if (!image && url.origin === current.origin && url.pathname.replace(/\/$/, '') === current.pathname.replace(/\/$/, '') && url.hash) return url.hash;
17
+ return url.href;
18
+ } catch { return undefined; }
19
+ }
20
+
21
+ export function printPolicy(base) {
22
+ const url = new URL(base);
23
+ if (!['http:', 'https:'].includes(url.protocol) || url.username || url.password) throw new Error('Print view requires a website origin');
24
+ // about:blank inherits access to its creator, but Firefox can resolve CSP 'self'
25
+ // against the blank document. Grant assets only to the explicit website origin.
26
+ return `default-src 'none'; img-src ${url.origin} https:; font-src ${url.origin}; style-src ${url.origin} 'unsafe-inline'; script-src 'none'; base-uri 'none'; form-action 'none'`;
27
+ }
28
+
29
+ /** Copy semantic content, not the website's controls or executable DOM. */
30
+ export function printNodes(node, base) {
31
+ if (node.nodeType === 3) return [{ text: node.textContent ?? '' }];
32
+ if (node.nodeType !== 1) return [];
33
+ const tag = node.localName.toLowerCase();
34
+ const classes = (node.getAttribute('class') ?? '').split(/\s+/);
35
+ if (blocked.has(tag) || classes.some(name => browserOnly.has(name))) return [];
36
+ if (tag === 'button') return [];
37
+ if (tag === 'input') return node.getAttribute('type') === 'checkbox' ? [{ tag: 'span', attrs: {}, children: [{ text: node.hasAttribute('checked') ? '☑ ' : '☐ ' }] }] : [];
38
+ const namespace = node.namespaceURI;
39
+ const math = namespace === 'http://www.w3.org/1998/Math/MathML';
40
+ const svg = namespace === 'http://www.w3.org/2000/svg';
41
+ let children = [...node.childNodes].flatMap(child => printNodes(child, base));
42
+ if (tag === 'details' || tag === 'summary') return [{ tag: tag === 'details' ? 'section' : 'h3', attrs: {}, children }];
43
+ if (!(math ? mathTags : svg ? svgTags : htmlTags).has(tag)) return children;
44
+ const attrs = {};
45
+ for (const attribute of [...node.attributes]) {
46
+ const name = attribute.name.toLowerCase(), value = attribute.value;
47
+ if (/^on/.test(name) || ['hidden', 'aria-hidden', 'srcset', 'tabindex', 'contenteditable'].includes(name)) continue;
48
+ if (name === 'href' || name === 'xlink:href') {
49
+ if (svg) { if (/^#[A-Za-z0-9_.:-]+$/.test(value)) attrs.href = value; }
50
+ else { const url = printUrl(value, base); if (url) attrs.href = url; }
51
+ } else if (name === 'src' && tag === 'img') {
52
+ const url = printUrl(node.currentSrc || value, base, true);
53
+ if (url) attrs.src = url;
54
+ } else if (name === 'crossorigin' && tag === 'img') {
55
+ // Empty and invalid CORS attribute values both mean anonymous in HTML.
56
+ attrs.crossorigin = value.toLowerCase() === 'use-credentials' ? 'use-credentials' : 'anonymous';
57
+ } else if (name === 'referrerpolicy' && tag === 'img') {
58
+ const policy = value.toLowerCase();
59
+ if (referrerPolicies.has(policy)) attrs.referrerpolicy = policy;
60
+ } else if (name === 'style') {
61
+ // Site-theme code tokens remain readable through the print theme's variables.
62
+ if (/^(?:color\s*:\s*(?:#[0-9a-f]{3,8}|var\(--code-(?:foreground|token-[a-z-]+)\));?\s*)+$/i.test(value)) attrs.style = value;
63
+ } else if (attributes.has(name) || math && mathAttributes.has(name)) attrs[name] = value;
64
+ // CSS escapes can hide url() and its protocol; retain only literal SVG values.
65
+ else if (svg && svgAttributes.has(name) && !/[\\\u0000-\u001f]|javascript:|https?:|data:|url\((?!\s*#)/i.test(value)) attrs[attribute.name] = value;
66
+ }
67
+ if (tag === 'img') {
68
+ if (!attrs.src) return [{ tag: 'p', attrs: {}, children: [{ text: attrs.alt ? `Image: ${attrs.alt}` : 'Image unavailable for printing.' }] }];
69
+ attrs.loading = 'eager';
70
+ }
71
+ if (tag === 'a' && attrs.href && !attrs.href.startsWith('#')) { attrs.target = '_blank'; attrs.rel = 'noopener noreferrer'; }
72
+ const label = node.getAttribute('data-reader-print-label');
73
+ if (label) children = [{ tag: 'p', attrs: { class: 'pdf-reader-label' }, children: [{ text: label }] }, ...children];
74
+ return [{ tag: node.localName, ...(math || svg ? { namespace } : {}), attrs, children }];
75
+ }
76
+
77
+ export function materialize(doc, node) {
78
+ if (node.text !== undefined) return doc.createTextNode(node.text);
79
+ const result = node.namespace ? doc.createElementNS(node.namespace, node.tag) : doc.createElement(node.tag);
80
+ // Setting src can start a request immediately, even on a detached image.
81
+ for (const [name, value] of Object.entries(node.attrs)) if (name !== 'src') result.setAttribute(name, value);
82
+ if (node.attrs.src !== undefined) result.setAttribute('src', node.attrs.src);
83
+ for (const child of node.children) result.append(materialize(doc, child));
84
+ return result;
85
+ }
86
+ function element(doc, tag, text, className) {
87
+ const result = doc.createElement(tag);
88
+ if (text !== undefined) result.textContent = text;
89
+ if (className) result.className = className;
90
+ return result;
91
+ }
92
+ function marginContent(value, title) {
93
+ return value.split(/(\{title\}|\{page\}|\{pages\})/).filter(Boolean).map(part => part === '{page}' ? 'counter(page)' : part === '{pages}' ? 'counter(pages)' : JSON.stringify(part === '{title}' ? title : part)).join(' ');
94
+ }
95
+
96
+ export async function populatePrintView(view, source, metadata, settings, signal) {
97
+ if (view.closed || signal?.aborted) throw new Error('Print view was closed or cancelled');
98
+ const doc = view.document;
99
+ view.opener = null;
100
+ doc.documentElement.lang = metadata.publication?.language ?? source.documentElement.lang ?? 'en';
101
+ doc.head.replaceChildren();
102
+ doc.title = metadata.title;
103
+ const charset = element(doc, 'meta'); charset.setAttribute('charset', 'utf-8'); doc.head.append(charset);
104
+ const policy = element(doc, 'meta'); policy.setAttribute('http-equiv', 'Content-Security-Policy');
105
+ policy.setAttribute('content', printPolicy(source.location.href));
106
+ doc.head.append(policy);
107
+ const stylesheet = element(doc, 'link'); stylesheet.rel = 'stylesheet'; stylesheet.href = new URL(settings.stylesheet, source.location.href).href;
108
+ if (new URL(stylesheet.href).origin !== source.location.origin) throw new Error('Print theme must be local');
109
+ const styleReady = new Promise((resolve, reject) => { stylesheet.onload = resolve; stylesheet.onerror = () => reject(new Error('Print theme could not load')); });
110
+ doc.head.append(stylesheet);
111
+ const margins = element(doc, 'style');
112
+ margins.textContent = `@page{${settings.header ? `@top-center{content:${marginContent(settings.header, metadata.title)};font:9px Arial,sans-serif;color:#555}` : ''}${settings.footer ? `@bottom-center{content:${marginContent(settings.footer, metadata.title)};font:9px Arial,sans-serif;color:#555}` : ''}}`;
113
+ doc.head.append(margins);
114
+ const tools = element(doc, 'nav', undefined, 'pdf-print-tools'); tools.setAttribute('aria-label', 'Print controls');
115
+ const print = element(doc, 'button', 'Print PDF'); print.type = 'button'; print.disabled = true;
116
+ const close = element(doc, 'button', 'Close'); close.type = 'button'; close.addEventListener('click', () => view.close());
117
+ const note = element(doc, 'span', 'Preparing document…', 'pdf-print-note'); note.setAttribute('role', 'status');
118
+ tools.append(print, close, note);
119
+ doc.body.replaceChildren(tools);
120
+ const publication = Object.entries(metadata.publication ?? {}).filter(([, value]) => value !== undefined).map(([key, value]) => `${key}: ${value}`).join(' · ');
121
+ if (settings.cover) {
122
+ const cover = element(doc, 'section', undefined, 'pdf-cover');
123
+ if (settings.logo) { const logo = element(doc, 'img', undefined, 'pdf-logo'); logo.src = new URL(settings.logo, source.location.href).href; logo.alt = ''; cover.append(logo); }
124
+ cover.append(element(doc, 'h1', metadata.title));
125
+ if (settings.subtitle) cover.append(element(doc, 'p', settings.subtitle, 'pdf-subtitle'));
126
+ if (publication) cover.append(element(doc, 'p', publication, 'pdf-publication'));
127
+ doc.body.append(cover);
128
+ }
129
+ const chapter = element(doc, 'article', undefined, 'pdf-chapter');
130
+ const header = source.querySelector('main.content > .page-head');
131
+ if (header) for (const node of printNodes(header, source.location.href)) chapter.append(materialize(doc, node));
132
+ else chapter.append(element(doc, 'h1', metadata.title));
133
+ if (publication && !settings.cover) (chapter.querySelector('.page-head') ?? chapter).append(element(doc, 'p', publication, 'pdf-page-meta'));
134
+ const article = source.querySelector('main.content > article');
135
+ if (!article) throw new Error('Page content is unavailable');
136
+ for (const node of [...article.childNodes].flatMap(child => printNodes(child, source.location.href))) chapter.append(materialize(doc, node));
137
+ for (const examples of source.querySelectorAll('.examples')) for (const node of printNodes(examples, source.location.href)) chapter.append(materialize(doc, node));
138
+ if (settings.toc) {
139
+ const toc = element(doc, 'nav', undefined, 'pdf-toc'); toc.setAttribute('aria-label', 'Contents'); toc.append(element(doc, 'h1', 'Contents'));
140
+ const list = element(doc, 'ol');
141
+ let index = 0;
142
+ const usedIds = new Set([...chapter.querySelectorAll('[id]')].map(node => node.id));
143
+ for (const heading of chapter.querySelectorAll('h2,h3')) {
144
+ if (!heading.id) {
145
+ let id; do { id = `pdf-section-${++index}`; } while (usedIds.has(id));
146
+ heading.id = id; usedIds.add(id);
147
+ }
148
+ const item = element(doc, 'li'), link = element(doc, 'a', heading.textContent); link.setAttribute('href', `#${heading.id}`); item.append(link); list.append(item);
149
+ }
150
+ toc.append(list); doc.body.append(toc);
151
+ }
152
+ doc.body.append(chapter);
153
+ await styleReady;
154
+ await doc.fonts.ready;
155
+ for (const font of settings.fonts ?? []) {
156
+ const loaded = await doc.fonts.load(`${font.style ?? 'normal'} ${font.weight ?? 'normal'} 12px ${JSON.stringify(font.family)}`);
157
+ if (!loaded.length) throw new Error('A print font could not load');
158
+ }
159
+ await Promise.all([...doc.images].map(image => image.complete ? (image.naturalWidth ? Promise.resolve() : Promise.reject(new Error('A print image could not load'))) : new Promise((resolve, reject) => { image.onload = resolve; image.onerror = () => reject(new Error('A print image could not load')); })));
160
+ if (view.closed || signal?.aborted) throw new Error('Print view was closed or cancelled');
161
+ print.disabled = false; note.textContent = 'Choose Save as PDF in the print dialog.';
162
+ const trigger = () => { if (!view.closed) { view.focus(); view.print(); } };
163
+ print.addEventListener('click', trigger);
164
+ return trigger;
165
+ }
@@ -0,0 +1,63 @@
1
+ import { populatePrintView } from './print-view.js';
2
+
3
+ const buttons = document.querySelectorAll('[data-print-page]');
4
+ const status = document.querySelector('[data-print-status]');
5
+ let opened = [];
6
+ let printing = false;
7
+
8
+ const prepare = () => {
9
+ if (printing) return;
10
+ printing = true;
11
+ opened = [...document.querySelectorAll('main details:not([open]), .examples details:not([open])')];
12
+ for (const details of opened) details.open = true;
13
+ };
14
+ const restore = () => {
15
+ for (const details of opened) details.open = false;
16
+ opened = [];
17
+ printing = false;
18
+ };
19
+ window.addEventListener('beforeprint', prepare);
20
+ window.addEventListener('afterprint', restore);
21
+ for (const button of buttons) {
22
+ button.hidden = false;
23
+ button.addEventListener('click', async () => {
24
+ if (button.disabled) return;
25
+ button.disabled = true;
26
+ if (status) status.textContent = '';
27
+ let view;
28
+ try { view = window.open('', '_blank'); } catch { /* Browser policy may refuse the new view. */ }
29
+ if (!view) {
30
+ button.disabled = false;
31
+ if (status) status.textContent = 'Allow the print view to open, or use your browser’s Print command.';
32
+ return;
33
+ }
34
+ const controller = new AbortController();
35
+ let timer;
36
+ try {
37
+ view.document.title = 'Print preview';
38
+ view.document.body.textContent = 'Preparing document…';
39
+ const settingsUrl = new URL(button.dataset.printSettings, document.location.href);
40
+ if (settingsUrl.origin !== document.location.origin) throw new Error('Print settings must be local');
41
+ const work = async () => {
42
+ const response = await fetch(settingsUrl.href, { credentials: 'same-origin', signal: controller.signal });
43
+ if (!response.ok) throw new Error('Print theme could not load');
44
+ const text = await response.text();
45
+ if (text.length > 65536) throw new Error('Print settings are too large');
46
+ const settings = JSON.parse(text);
47
+ if (settings.schemaVersion !== 1 || typeof settings.stylesheet !== 'string') throw new Error('Invalid print settings');
48
+ const metadata = JSON.parse(document.querySelector('[data-print-page-meta]').textContent);
49
+ return populatePrintView(view, document, metadata, settings, controller.signal);
50
+ };
51
+ const print = await Promise.race([work(), new Promise((_, reject) => {
52
+ timer = setTimeout(() => { controller.abort(); reject(new Error('Print preparation timed out')); }, 20000);
53
+ })]);
54
+ clearTimeout(timer); timer = undefined;
55
+ print();
56
+ } catch {
57
+ restore();
58
+ const message = 'Could not prepare the print view. Use your browser’s Print command, or try again.';
59
+ if (status) status.textContent = message;
60
+ try { if (!view.closed) view.document.body.append(view.document.createTextNode(message)); } catch { /* The reader may have navigated away. */ }
61
+ } finally { clearTimeout(timer); button.disabled = false; }
62
+ });
63
+ }
@@ -13,6 +13,7 @@ export type ArtifactPage = {
13
13
  sections?: import('./markdown-sections.js').MarkdownSection[];
14
14
  };
15
15
  export type ArtifactAssemblyInput = {
16
+ extraArtifacts?: Map<string, string | Buffer>;
16
17
  base: string;
17
18
  basePath: string;
18
19
  publicPath: (route: string) => string;
@@ -38,6 +38,8 @@ export async function assembleArtifacts(input) {
38
38
  artifactDirectories.add(parent);
39
39
  artifacts.set(normalized, data);
40
40
  };
41
+ for (const [route, bytes] of input.extraArtifacts ?? [])
42
+ addArtifact(route, bytes);
41
43
  const addPublicArtifact = (route, data) => addArtifact(publicPath(route), data);
42
44
  const addMetadataArtifact = (route, data) => {
43
45
  for (const url of new Set([route, publicPath(route)]))
@@ -193,7 +193,7 @@ export async function captureSnapshotInputs(configFile, options = {}) {
193
193
  for (const key of Object.keys(value))
194
194
  await visit(value[key], [...segments, key]);
195
195
  }
196
- else if (typeof value === 'string' && (['file', 'path', 'module', 'manifest', 'customCss', 'customReaderModule', 'body', 'template', 'favicon', 'logo'].includes(segments.at(-1)) || segments.at(-2) === 'files') && !/^(?:[a-z][a-z0-9+.-]*:|#)/i.test(value)) {
196
+ else if (typeof value === 'string' && (['file', 'path', 'module', 'manifest', 'customCss', 'customReaderModule', 'body', 'template', 'favicon', 'logo'].includes(segments.at(-1)) || segments.at(-2) === 'files' || (segments[0] === 'outputs' && segments[1] === 'html' && segments[2] === 'highlighting' && segments.at(-1) === 'grammar') || (segments.includes('pdf') && (segments.at(-1) === 'stylesheet' || segments.includes('fonts') && segments.at(-1) === 'source'))) && !/^(?:[a-z][a-z0-9+.-]*:|#)/i.test(value)) {
197
197
  if (path.isAbsolute(value))
198
198
  throw new SnapshotError('unsupported_dependency', `Absolute dependency at ${segments.join('.')} cannot be isolated; use a relative path.`);
199
199
  if (segments[0] === 'components' && segments.at(-1) === 'module' && !value.startsWith('.'))
@@ -0,0 +1,9 @@
1
+ /** One shared print theme for the entire build; no page content is materialized. */
2
+ export declare function browserPrintAssets(configFile: string, options: {
3
+ basePath: string;
4
+ profile?: string;
5
+ style?: string;
6
+ }): Promise<{
7
+ artifacts: Map<string, string | Buffer<ArrayBufferLike>>;
8
+ dependencies: Set<string>;
9
+ }>;
@@ -0,0 +1,45 @@
1
+ import path from 'node:path';
2
+ import { parse } from 'parse5';
3
+ import { loadConfig } from './config.js';
4
+ import { projectPdfSettings } from './pdf-project.js';
5
+ import { preparePdfDocument } from './pdf-renderer.js';
6
+ import { sitePath } from './site-path.js';
7
+ /** One shared print theme for the entire build; no page content is materialized. */
8
+ export async function browserPrintAssets(configFile, options) {
9
+ const loaded = await loadConfig(configFile, { profile: options.profile });
10
+ const { config, files } = projectPdfSettings(loaded, configFile, options.style);
11
+ const prepared = await preparePdfDocument({
12
+ documents: [{ id: 'print-theme', title: '', html: '' }], title: '', assets: new Map(), publicBase: '/',
13
+ config: { ...config, cover: false, toc: false }, configFile,
14
+ assetRoots: files.map(file => path.dirname(file))
15
+ });
16
+ let css = '';
17
+ const findStyle = (node) => { if (node.tagName === 'style')
18
+ css += (node.childNodes ?? []).map((child) => child.value ?? '').join(''); for (const child of node.childNodes ?? [])
19
+ findStyle(child); };
20
+ findStyle(parse(prepared.html));
21
+ const artifacts = new Map();
22
+ let logo;
23
+ for (const [original, asset] of prepared.assets) {
24
+ const route = sitePath(options.basePath, `/assets/print/${path.posix.basename(original)}`);
25
+ artifacts.set(route, asset.bytes);
26
+ css = css.replaceAll(original, route);
27
+ if (original.startsWith('/__pdf_logo/'))
28
+ logo = route;
29
+ }
30
+ const margins = { top: '18mm', right: '18mm', bottom: '18mm', left: '18mm', ...config.margins };
31
+ css = `@page{size:${config.size ?? 'A4'};margin:${margins.top} ${margins.right} ${margins.bottom} ${margins.left}}\n` + css;
32
+ css += '\n.pdf-print-tools{display:flex;gap:10px;align-items:center;padding:12px 0;margin-bottom:24px;font:14px Arial,sans-serif}.pdf-print-tools button{font:inherit;padding:8px 12px;border:1px solid #89998e;border-radius:4px;cursor:pointer}.pdf-print-tools .pdf-print-note{margin-left:auto;color:#53665e;font-size:12px}.pdf-chapter .page-head h1{margin-bottom:8pt}.pdf-chapter .page-head .summary{font-size:10pt;line-height:1.5;margin:0 0 8pt}.pdf-chapter .page-head .pdf-page-meta{font-size:8pt;margin:5pt 0 12pt}.pdf-page-meta{color:var(--pdf-muted);font-size:9pt;margin-bottom:14pt}.pdf-chapter .eyebrow{color:var(--pdf-muted);font-size:9pt}.pdf-chapter .page-dates{display:flex;flex-wrap:wrap;gap:4pt 10pt;font-size:9pt;color:var(--pdf-muted)}.pdf-chapter .doc-callout{border-left:3pt solid var(--pdf-accent);padding:10pt 14pt;background:#f1f4ee;margin:14pt 0}.pdf-chapter details{display:block}.pdf-reader-label{font-weight:bold;margin-top:14pt}@media screen{html{background:#dfe5de}body{max-width:210mm;margin:24px auto;padding:18mm;background:white;box-shadow:0 8px 40px #173c3520}}@media print{.pdf-print-tools{display:none!important}}';
33
+ // A manual theme may start every chapter on a new page. When no cover or
34
+ // contents precede this chapter, that break can produce a blank first sheet.
35
+ css += '\n.pdf-print-tools + .pdf-chapter{break-before:auto}';
36
+ const stylesheet = sitePath(options.basePath, '/assets/print/theme.css');
37
+ artifacts.set(stylesheet, css);
38
+ const settings = {
39
+ schemaVersion: 1, stylesheet, ...(logo ? { logo } : {}), cover: config.cover ?? false,
40
+ toc: config.toc ?? false, subtitle: config.subtitle ?? '', header: config.header ?? '', footer: config.footer ?? '',
41
+ fonts: (config.fonts ?? []).map(({ family, weight, style }) => ({ family, weight, style }))
42
+ };
43
+ artifacts.set(sitePath(options.basePath, '/assets/print/settings.json'), JSON.stringify(settings));
44
+ return { artifacts, dependencies: new Set([...loaded.dependencies, ...files]) };
45
+ }