blume 2.1.3 → 2.2.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.
Files changed (377) hide show
  1. package/AGENTS.md +1 -1
  2. package/CHANGELOG.md +126 -0
  3. package/README.md +4 -4
  4. package/dist/cli/{chunk-74wfhv79.js → chunk-05f21r8h.js} +1 -1
  5. package/dist/cli/{chunk-9qzgg6mj.js → chunk-1cm0h819.js} +3 -3
  6. package/dist/cli/{chunk-nbyvgnnm.js → chunk-1rkxzg3n.js} +104 -47
  7. package/dist/cli/chunk-1rkxzg3n.js.map +36 -0
  8. package/dist/cli/{chunk-desvm72z.js → chunk-2nr29y95.js} +22 -26
  9. package/dist/cli/chunk-2nr29y95.js.map +13 -0
  10. package/dist/cli/{chunk-32khwcb6.js → chunk-49r370mb.js} +42 -69
  11. package/dist/cli/{chunk-32khwcb6.js.map → chunk-49r370mb.js.map} +9 -10
  12. package/dist/cli/{chunk-ms9z66nz.js → chunk-4rftmttv.js} +2 -2
  13. package/dist/cli/{chunk-s0bdnc7v.js → chunk-79bzzkx4.js} +5 -4
  14. package/dist/cli/chunk-79bzzkx4.js.map +10 -0
  15. package/dist/cli/{chunk-hvcxng5f.js → chunk-7tekknev.js} +6 -6
  16. package/dist/cli/{chunk-dv2z0rzf.js → chunk-8b489v1b.js} +38 -13
  17. package/dist/cli/{chunk-dv2z0rzf.js.map → chunk-8b489v1b.js.map} +5 -4
  18. package/dist/cli/{chunk-0pmb4tr4.js → chunk-8f8xw4x2.js} +1195 -222
  19. package/dist/cli/chunk-8f8xw4x2.js.map +44 -0
  20. package/dist/cli/chunk-96snx8yy.js +329 -0
  21. package/dist/cli/chunk-96snx8yy.js.map +13 -0
  22. package/dist/cli/{chunk-ckrcm08h.js → chunk-a3ydrqny.js} +48 -62
  23. package/dist/cli/{chunk-ckrcm08h.js.map → chunk-a3ydrqny.js.map} +4 -5
  24. package/dist/cli/{chunk-bxwhtvw8.js → chunk-ahc5gc9h.js} +5 -5
  25. package/dist/cli/{chunk-dpy8qdjf.js → chunk-as09pafp.js} +73 -182
  26. package/dist/cli/chunk-as09pafp.js.map +18 -0
  27. package/dist/cli/{chunk-tfycddbt.js → chunk-bjk964fp.js} +2 -2
  28. package/dist/cli/{chunk-gfc5fqvf.js → chunk-br0n8n3e.js} +18 -18
  29. package/dist/cli/{chunk-gfc5fqvf.js.map → chunk-br0n8n3e.js.map} +1 -1
  30. package/dist/cli/{chunk-297ajdcv.js → chunk-c69tw29j.js} +6 -6
  31. package/dist/cli/{chunk-297ajdcv.js.map → chunk-c69tw29j.js.map} +3 -3
  32. package/dist/cli/chunk-cn3dy3hz.js +146 -0
  33. package/dist/cli/chunk-cn3dy3hz.js.map +11 -0
  34. package/dist/cli/{chunk-124fvmzq.js → chunk-dm4mg9bj.js} +11064 -8742
  35. package/dist/cli/{chunk-124fvmzq.js.map → chunk-dm4mg9bj.js.map} +78 -64
  36. package/dist/cli/{chunk-dqhg54mr.js → chunk-dmx6cezx.js} +1 -1
  37. package/dist/cli/{chunk-60hz265r.js → chunk-dpcahcrb.js} +24 -11
  38. package/dist/cli/chunk-dpcahcrb.js.map +10 -0
  39. package/dist/cli/{chunk-a57yh0h9.js → chunk-dyk3r7jw.js} +7 -7
  40. package/dist/cli/{chunk-a57yh0h9.js.map → chunk-dyk3r7jw.js.map} +1 -1
  41. package/dist/cli/{chunk-twy7psdz.js → chunk-enk76qd5.js} +14 -10
  42. package/dist/cli/chunk-enk76qd5.js.map +10 -0
  43. package/dist/cli/{chunk-k3gr1nm9.js → chunk-fqy4n3q2.js} +13 -16
  44. package/dist/cli/{chunk-k3gr1nm9.js.map → chunk-fqy4n3q2.js.map} +3 -3
  45. package/dist/cli/{chunk-sc93apfs.js → chunk-frk2wdq1.js} +17 -17
  46. package/dist/cli/{chunk-sc93apfs.js.map → chunk-frk2wdq1.js.map} +2 -2
  47. package/dist/cli/{chunk-kdpzw38a.js → chunk-gprwej7h.js} +95 -7
  48. package/dist/cli/chunk-gprwej7h.js.map +11 -0
  49. package/dist/cli/chunk-kkrteb15.js +157 -0
  50. package/dist/cli/chunk-kkrteb15.js.map +10 -0
  51. package/dist/cli/{chunk-ave1wcbr.js → chunk-m9bp2hc1.js} +1 -1
  52. package/dist/cli/{chunk-58bt3hag.js → chunk-mrvh4c88.js} +4 -4
  53. package/dist/cli/{chunk-5cmts6dv.js → chunk-p8zg3a2h.js} +7 -7
  54. package/dist/cli/{chunk-5cmts6dv.js.map → chunk-p8zg3a2h.js.map} +1 -1
  55. package/dist/cli/{chunk-0w6hs479.js → chunk-rkqae7kh.js} +30 -18
  56. package/dist/cli/chunk-rkqae7kh.js.map +10 -0
  57. package/dist/cli/{chunk-bqxaqn5p.js → chunk-t5xx74rd.js} +8 -8
  58. package/dist/cli/{chunk-bqxaqn5p.js.map → chunk-t5xx74rd.js.map} +1 -1
  59. package/dist/cli/{chunk-1f882900.js → chunk-th0pgaj5.js} +2 -2
  60. package/dist/cli/{chunk-dhwtqnnd.js → chunk-vnbvhgmd.js} +2 -2
  61. package/dist/cli/{chunk-gxtg6ed2.js → chunk-vzy9rh6y.js} +21 -21
  62. package/dist/cli/{chunk-gxtg6ed2.js.map → chunk-vzy9rh6y.js.map} +3 -3
  63. package/dist/cli/{chunk-j1x92mx9.js → chunk-w6e455pz.js} +1 -1
  64. package/dist/cli/chunk-wehv26ry.js +155 -0
  65. package/dist/cli/chunk-wehv26ry.js.map +11 -0
  66. package/dist/cli/{chunk-y4msrwm9.js → chunk-xgq4eytj.js} +15 -15
  67. package/dist/cli/{chunk-36bxs9c7.js → chunk-xt0qvz1t.js} +27 -23
  68. package/dist/cli/chunk-xt0qvz1t.js.map +10 -0
  69. package/dist/cli/index.js +78 -42
  70. package/dist/cli/index.js.map +7 -6
  71. package/dist/types/ai/changelog-components.d.ts +18 -0
  72. package/dist/types/ai/changelog-markdown.d.ts +12 -0
  73. package/dist/types/ai/openapi-components.d.ts +7 -7
  74. package/dist/types/ai/operation-details.d.ts +39 -0
  75. package/dist/types/ai/relative-links.d.ts +3 -1
  76. package/dist/types/ai/serializers.d.ts +6 -4
  77. package/dist/types/analytics/head.d.ts +4 -2
  78. package/dist/types/analytics/script.d.ts +8 -2
  79. package/dist/types/components/content/youtube.d.ts +20 -0
  80. package/dist/types/components/layout/nav-utils.d.ts +8 -0
  81. package/dist/types/components/openapi/example-panels.d.ts +33 -0
  82. package/dist/types/components/openapi/helpers.d.ts +53 -11
  83. package/dist/types/components/openapi/snippets.d.ts +7 -1
  84. package/dist/types/core/alert-diagnostics.d.ts +10 -0
  85. package/dist/types/core/builtin-tags.d.ts +16 -0
  86. package/dist/types/core/changelog-list.d.ts +48 -0
  87. package/dist/types/core/closest-match.d.ts +6 -0
  88. package/dist/types/core/code-fence-diagnostics.d.ts +18 -0
  89. package/dist/types/core/code-fences.d.ts +8 -1
  90. package/dist/types/core/config-input.d.ts +1 -1
  91. package/dist/types/core/content-assets.d.ts +16 -18
  92. package/dist/types/core/diagnostics.d.ts +14 -0
  93. package/dist/types/core/directive-diagnostics.d.ts +22 -8
  94. package/dist/types/core/former-routes.d.ts +14 -0
  95. package/dist/types/core/github.d.ts +5 -4
  96. package/dist/types/core/heading-markers.d.ts +65 -2
  97. package/dist/types/core/hidden-pages.d.ts +3 -0
  98. package/dist/types/core/includes.d.ts +23 -0
  99. package/dist/types/core/last-modified.d.ts +18 -6
  100. package/dist/types/core/links.d.ts +42 -2
  101. package/dist/types/core/load-module.d.ts +19 -4
  102. package/dist/types/core/manifest.d.ts +14 -1
  103. package/dist/types/core/markdown-mirrors.d.ts +22 -0
  104. package/dist/types/core/mdx-elements.d.ts +32 -0
  105. package/dist/types/core/mdx-syntax.d.ts +100 -0
  106. package/dist/types/core/meta-diagnostics.d.ts +7 -0
  107. package/dist/types/core/meta.d.ts +32 -5
  108. package/dist/types/core/nav-diagnostics.d.ts +9 -2
  109. package/dist/types/core/navigation.d.ts +13 -0
  110. package/dist/types/core/ordering-prefix.d.ts +6 -0
  111. package/dist/types/core/pinned-ids.d.ts +23 -0
  112. package/dist/types/core/project-graph.d.ts +9 -1
  113. package/dist/types/core/redirect-patterns.d.ts +18 -0
  114. package/dist/types/core/relative-files.d.ts +31 -0
  115. package/dist/types/core/schema.d.ts +267 -7
  116. package/dist/types/core/sources/normalize.d.ts +101 -9
  117. package/dist/types/core/sources/types.d.ts +16 -0
  118. package/dist/types/core/static-files.d.ts +10 -0
  119. package/dist/types/core/static-host.d.ts +42 -0
  120. package/dist/types/core/svg-images.d.ts +26 -0
  121. package/dist/types/core/syntax-diagnostics.d.ts +14 -0
  122. package/dist/types/core/types.d.ts +42 -1
  123. package/dist/types/deploy/generated-files.d.ts +41 -0
  124. package/dist/types/deploy/redirects.d.ts +21 -7
  125. package/dist/types/markdown/code-title.d.ts +9 -5
  126. package/dist/types/markdown/directives.d.ts +8 -0
  127. package/dist/types/markdown/fence-language.d.ts +51 -0
  128. package/dist/types/markdown/fence-meta.d.ts +5 -0
  129. package/dist/types/markdown/github-alerts.d.ts +35 -0
  130. package/dist/types/openapi/asyncapi.d.ts +6 -5
  131. package/dist/types/openapi/checks.d.ts +10 -5
  132. package/dist/types/openapi/model.d.ts +55 -8
  133. package/dist/types/openapi/parse.d.ts +4 -1
  134. package/dist/types/sources/filesystem.d.ts +14 -4
  135. package/dist/types/sources/mdx-remote.d.ts +6 -3
  136. package/dist/types/sources/registry.d.ts +3 -3
  137. package/dist/types/theme/color.d.ts +33 -0
  138. package/dist/types/theme/palette.d.ts +122 -0
  139. package/docs/01-quickstart.mdx +1 -1
  140. package/docs/02-deployment.mdx +23 -8
  141. package/docs/04-migrating.mdx +16 -4
  142. package/docs/advanced/changelog.mdx +27 -1
  143. package/docs/advanced/skills.mdx +1 -1
  144. package/docs/cli/audit.mdx +1 -1
  145. package/docs/cli/doctor.mdx +2 -2
  146. package/docs/cli/evals.mdx +1 -1
  147. package/docs/cli/index.mdx +4 -2
  148. package/docs/cli/validate.mdx +10 -7
  149. package/docs/configuration/analytics.mdx +2 -0
  150. package/docs/configuration/customization.mdx +3 -1
  151. package/docs/configuration/index.mdx +2 -2
  152. package/docs/configuration/theming.mdx +5 -1
  153. package/docs/content/components.mdx +26 -6
  154. package/docs/content/frontmatter.mdx +1 -1
  155. package/docs/content/includes.mdx +6 -2
  156. package/docs/content/index.mdx +11 -1
  157. package/docs/content/islands.mdx +2 -0
  158. package/docs/content/meta.mdx +9 -3
  159. package/docs/content/navigation.mdx +14 -6
  160. package/docs/content/sources/obsidian.mdx +1 -1
  161. package/docs/content/sources/remote-mdx.mdx +3 -1
  162. package/docs/content/syntax.mdx +76 -8
  163. package/docs/content/variables.mdx +3 -1
  164. package/docs/discoverability/agent-discovery.mdx +1 -1
  165. package/docs/discoverability/json-api.mdx +1 -1
  166. package/docs/discoverability/llms-txt.mdx +1 -1
  167. package/docs/discoverability/markdown.mdx +2 -2
  168. package/docs/discoverability/open-graph.mdx +1 -1
  169. package/docs/discoverability/sitemap-and-robots.mdx +1 -1
  170. package/docs/references/graphql.mdx +2 -0
  171. package/docs/references/openapi.mdx +45 -7
  172. package/package.json +4 -4
  173. package/skills/blume/SKILL.md +2 -2
  174. package/skills/blume-migrate/SKILL.md +49 -27
  175. package/skills/blume-migrate/references/docsify.md +262 -0
  176. package/skills/blume-migrate/references/docus.md +298 -0
  177. package/skills/blume-migrate/references/docusaurus.md +3 -3
  178. package/skills/blume-migrate/references/fern.md +327 -0
  179. package/skills/blume-migrate/references/fumadocs.md +1 -1
  180. package/skills/blume-migrate/references/gitbook.md +267 -0
  181. package/skills/blume-migrate/references/github-wiki.md +259 -0
  182. package/skills/blume-migrate/references/jekyll.md +253 -0
  183. package/skills/blume-migrate/references/mdbook.md +256 -0
  184. package/skills/blume-migrate/references/mintlify.md +5 -5
  185. package/skills/blume-migrate/references/mkdocs.md +236 -0
  186. package/skills/blume-migrate/references/monorepo.md +20 -8
  187. package/skills/blume-migrate/references/nextra.md +2 -2
  188. package/skills/blume-migrate/references/readme.md +257 -0
  189. package/skills/blume-migrate/references/redocly.md +198 -0
  190. package/skills/blume-migrate/references/starlight.md +3 -3
  191. package/skills/blume-migrate/references/vitepress.md +204 -0
  192. package/skills/blume-migrate/references/vuepress.md +271 -0
  193. package/skills/blume-migrate/scripts/docsify-codemod.mjs +2387 -0
  194. package/skills/blume-migrate/scripts/docus-codemod.mjs +2542 -0
  195. package/skills/blume-migrate/scripts/fern-codemod.mjs +2633 -0
  196. package/skills/blume-migrate/scripts/github-wiki-codemod.mjs +2808 -0
  197. package/skills/blume-migrate/scripts/include-excerpts.mjs +649 -0
  198. package/skills/blume-migrate/scripts/jekyll-codemod.mjs +3649 -0
  199. package/skills/blume-migrate/scripts/mintlify-codemod.mjs +17 -13
  200. package/skills/blume-migrate/scripts/mkdocs-codemod.mjs +2526 -0
  201. package/skills/blume-migrate/scripts/operation-routes.mjs +508 -0
  202. package/skills/blume-migrate/scripts/pin-heading-ids.mjs +1049 -0
  203. package/skills/blume-migrate/scripts/readme-codemod.mjs +4139 -0
  204. package/skills/blume-migrate/scripts/vitepress-codemod.mjs +1511 -0
  205. package/src/ai/changelog-components.ts +24 -0
  206. package/src/ai/changelog-markdown.ts +45 -41
  207. package/src/ai/component-markdown.ts +22 -9
  208. package/src/ai/llms.ts +36 -5
  209. package/src/ai/markdown.ts +2 -2
  210. package/src/ai/openapi-components.ts +25 -9
  211. package/src/ai/operation-details.ts +208 -0
  212. package/src/ai/relative-links.ts +48 -8
  213. package/src/ai/serializers.ts +7 -6
  214. package/src/ai/site-skill.ts +6 -2
  215. package/src/analytics/head.ts +11 -5
  216. package/src/analytics/script.ts +13 -3
  217. package/src/astro/generate.ts +32 -6
  218. package/src/astro/index.ts +1 -0
  219. package/src/astro/integration.ts +27 -1
  220. package/src/astro/mdx-source-errors.ts +46 -0
  221. package/src/astro/templates.ts +89 -185
  222. package/src/audit/checks/redirects.ts +5 -1
  223. package/src/audit/redirects.ts +36 -19
  224. package/src/audit/report.ts +22 -9
  225. package/src/audit/run.ts +12 -28
  226. package/src/audit/terms.ts +23 -0
  227. package/src/audit/url.ts +2 -2
  228. package/src/cli/build-failure.ts +256 -7
  229. package/src/cli/command-meta.ts +1 -1
  230. package/src/cli/commands/audit.ts +1 -1
  231. package/src/cli/commands/build.ts +12 -3
  232. package/src/cli/commands/check.ts +13 -2
  233. package/src/cli/commands/dev.ts +1 -1
  234. package/src/cli/commands/doctor.ts +4 -2
  235. package/src/cli/commands/init.ts +3 -3
  236. package/src/cli/commands/preview.ts +17 -1
  237. package/src/cli/commands/validate.ts +22 -10
  238. package/src/cli/prepare.ts +1 -1
  239. package/src/cli/preview-pages.ts +161 -0
  240. package/src/cli/unknown-flags.ts +1 -42
  241. package/src/components/code-copy.ts +51 -0
  242. package/src/components/code-language.ts +75 -0
  243. package/src/components/content/AccordionItem.astro +16 -1
  244. package/src/components/content/Callout.astro +11 -1
  245. package/src/components/content/Changelog.astro +189 -0
  246. package/src/components/content/CodeBlock.astro +8 -11
  247. package/src/components/content/Tabs.astro +16 -2
  248. package/src/components/content/YouTube.astro +6 -4
  249. package/src/components/content/code-block.ts +33 -0
  250. package/src/components/content/content-strings.ts +5 -1
  251. package/src/components/content/inline-markdown.ts +35 -8
  252. package/src/components/content/youtube.ts +49 -7
  253. package/src/components/layout/Analytics.astro +5 -1
  254. package/src/components/layout/NavTree.astro +8 -3
  255. package/src/components/layout/RootLayout.astro +18 -31
  256. package/src/components/layout/nav-utils.ts +14 -0
  257. package/src/components/layout/search/pagefind.ts +2 -1
  258. package/src/components/openapi/Operation.astro +4 -0
  259. package/src/components/openapi/PanelTabs.astro +9 -3
  260. package/src/components/openapi/RequestBody.astro +42 -8
  261. package/src/components/openapi/RequestPanel.astro +34 -53
  262. package/src/components/openapi/example-panels.ts +110 -0
  263. package/src/components/openapi/helpers.ts +195 -20
  264. package/src/components/openapi/operation-model.ts +63 -26
  265. package/src/components/openapi/request.ts +13 -0
  266. package/src/components/openapi/snippets.ts +14 -2
  267. package/src/core/alert-diagnostics.ts +89 -0
  268. package/src/core/builtin-tags.ts +76 -0
  269. package/src/core/changelog-list.ts +87 -0
  270. package/src/core/closest-match.ts +41 -0
  271. package/src/core/code-fence-diagnostics.ts +267 -0
  272. package/src/core/code-fences.ts +43 -7
  273. package/src/core/component-diagnostics.ts +65 -12
  274. package/src/core/config-input.ts +1 -1
  275. package/src/core/config.ts +46 -1
  276. package/src/core/content-assets.ts +64 -144
  277. package/src/core/diagnostics.ts +36 -3
  278. package/src/core/directive-diagnostics.ts +265 -47
  279. package/src/core/former-routes.ts +46 -0
  280. package/src/core/git-ignored.ts +53 -0
  281. package/src/core/github.ts +5 -4
  282. package/src/core/heading-markers.ts +156 -2
  283. package/src/core/hidden-pages.ts +64 -0
  284. package/src/core/includes.ts +181 -41
  285. package/src/core/last-modified.ts +47 -10
  286. package/src/core/links.ts +438 -83
  287. package/src/core/load-module.ts +98 -9
  288. package/src/core/manifest.ts +12 -9
  289. package/src/core/markdown-mirrors.ts +52 -0
  290. package/src/core/mdx-elements.ts +153 -0
  291. package/src/core/mdx-syntax.ts +377 -0
  292. package/src/core/meta-diagnostics.ts +134 -0
  293. package/src/core/meta.ts +154 -55
  294. package/src/core/nav-diagnostics.ts +16 -4
  295. package/src/core/navigation.ts +70 -10
  296. package/src/core/ordering-prefix.ts +22 -2
  297. package/src/core/pinned-ids.ts +141 -0
  298. package/src/core/project-graph.ts +221 -29
  299. package/src/core/redirect-patterns.ts +41 -0
  300. package/src/core/relative-files.ts +145 -0
  301. package/src/core/schema.ts +17 -3
  302. package/src/core/sources/mdx-remote.ts +75 -14
  303. package/src/core/sources/normalize.ts +885 -179
  304. package/src/core/sources/notion.ts +5 -4
  305. package/src/core/sources/obsidian.ts +2 -5
  306. package/src/core/sources/types.ts +17 -0
  307. package/src/core/static-files.ts +40 -0
  308. package/src/core/static-host.ts +125 -0
  309. package/src/core/svg-images.ts +98 -0
  310. package/src/core/syntax-diagnostics.ts +491 -0
  311. package/src/core/types.ts +43 -1
  312. package/src/deploy/artifacts.ts +2 -28
  313. package/src/deploy/generated-files.ts +128 -0
  314. package/src/deploy/platforms/vercel.ts +25 -1
  315. package/src/deploy/redirects.ts +43 -16
  316. package/src/deploy/sitemap.ts +2 -1
  317. package/src/markdown/base-links.ts +87 -6
  318. package/src/markdown/code-title.ts +29 -13
  319. package/src/markdown/content-assets.ts +210 -0
  320. package/src/markdown/directives.ts +69 -5
  321. package/src/markdown/fence-language.ts +106 -0
  322. package/src/markdown/fence-meta.ts +42 -0
  323. package/src/markdown/github-alerts.ts +172 -0
  324. package/src/markdown/heading-anchors.ts +265 -56
  325. package/src/markdown/include.ts +30 -3
  326. package/src/markdown/index.ts +145 -40
  327. package/src/markdown/prompt-text.ts +95 -0
  328. package/src/markdown/relative-links.ts +37 -2
  329. package/src/markdown/route-snapshot.ts +22 -12
  330. package/src/markdown/svg-fallback.ts +51 -0
  331. package/src/markdown/title-heading.ts +134 -0
  332. package/src/migrate/migrate.ts +127 -2
  333. package/src/og/card.ts +14 -4
  334. package/src/openapi/asyncapi.ts +14 -6
  335. package/src/openapi/checks.ts +160 -15
  336. package/src/openapi/model.ts +172 -33
  337. package/src/openapi/parse.ts +13 -4
  338. package/src/openapi/render-mdx.ts +55 -9
  339. package/src/openapi/source.ts +87 -6
  340. package/src/registry/eject.ts +0 -1
  341. package/src/search/documents.ts +10 -2
  342. package/src/sources/filesystem.ts +28 -3
  343. package/src/sources/mdx-remote.ts +7 -15
  344. package/src/theme/color.ts +14 -0
  345. package/src/theme/entry.ts +22 -1
  346. package/src/theme/palette.ts +74 -1
  347. package/dist/cli/chunk-0pmb4tr4.js.map +0 -35
  348. package/dist/cli/chunk-0w6hs479.js.map +0 -10
  349. package/dist/cli/chunk-150jk7km.js +0 -52
  350. package/dist/cli/chunk-150jk7km.js.map +0 -10
  351. package/dist/cli/chunk-36bxs9c7.js.map +0 -10
  352. package/dist/cli/chunk-53jqw8z0.js +0 -89
  353. package/dist/cli/chunk-53jqw8z0.js.map +0 -10
  354. package/dist/cli/chunk-60hz265r.js.map +0 -10
  355. package/dist/cli/chunk-desvm72z.js.map +0 -14
  356. package/dist/cli/chunk-dpy8qdjf.js.map +0 -17
  357. package/dist/cli/chunk-es8h0v3j.js +0 -441
  358. package/dist/cli/chunk-es8h0v3j.js.map +0 -11
  359. package/dist/cli/chunk-kdpzw38a.js.map +0 -11
  360. package/dist/cli/chunk-nbyvgnnm.js.map +0 -36
  361. package/dist/cli/chunk-s0bdnc7v.js.map +0 -10
  362. package/dist/cli/chunk-twy7psdz.js.map +0 -10
  363. package/dist/cli/chunk-ydp3065p.js +0 -64
  364. package/dist/cli/chunk-ydp3065p.js.map +0 -10
  365. /package/dist/cli/{chunk-74wfhv79.js.map → chunk-05f21r8h.js.map} +0 -0
  366. /package/dist/cli/{chunk-9qzgg6mj.js.map → chunk-1cm0h819.js.map} +0 -0
  367. /package/dist/cli/{chunk-ms9z66nz.js.map → chunk-4rftmttv.js.map} +0 -0
  368. /package/dist/cli/{chunk-hvcxng5f.js.map → chunk-7tekknev.js.map} +0 -0
  369. /package/dist/cli/{chunk-bxwhtvw8.js.map → chunk-ahc5gc9h.js.map} +0 -0
  370. /package/dist/cli/{chunk-tfycddbt.js.map → chunk-bjk964fp.js.map} +0 -0
  371. /package/dist/cli/{chunk-dqhg54mr.js.map → chunk-dmx6cezx.js.map} +0 -0
  372. /package/dist/cli/{chunk-ave1wcbr.js.map → chunk-m9bp2hc1.js.map} +0 -0
  373. /package/dist/cli/{chunk-58bt3hag.js.map → chunk-mrvh4c88.js.map} +0 -0
  374. /package/dist/cli/{chunk-1f882900.js.map → chunk-th0pgaj5.js.map} +0 -0
  375. /package/dist/cli/{chunk-dhwtqnnd.js.map → chunk-vnbvhgmd.js.map} +0 -0
  376. /package/dist/cli/{chunk-j1x92mx9.js.map → chunk-w6e455pz.js.map} +0 -0
  377. /package/dist/cli/{chunk-y4msrwm9.js.map → chunk-xgq4eytj.js.map} +0 -0
@@ -23,7 +23,9 @@ export default defineMeta({
23
23
 
24
24
  Every field is optional — set only what you want to override.
25
25
 
26
- Blume only reads `meta.ts` files from folders your content covers: one under a folder your content `exclude` globs skip, or outside every `include` glob, is never imported. With `root: "."` and `exclude: ["**/_*", "**/.*", "src/**"]`, an unrelated `src/lib/meta.ts` is left alone. An `exclude` replaces the default `["**/_*", "**/.*"]` rather than adding to it, so list those two as well to keep `_`-prefixed partials and dot-files unpublished.
26
+ Blume only reads `meta.ts` files from folders your content covers: one under a folder your content `exclude` globs skip, or outside every `include` glob, is never imported. With `root: "."` and `exclude: ["src/**"]`, an unrelated `src/lib/meta.ts` is left alone.
27
+
28
+ A `meta.ts` outside every `include` glob is still ignored when its folder is a sidebar group, like a reference's [tag folder](#generated-groups) on a site whose `include` lists only `guides/**`. Blume warns about that one as `BLUME_META_OUTSIDE_INCLUDE`: add an `include` glob that reaches the folder, such as `"api/**/*.{md,mdx}"`, so Blume reads it.
27
29
 
28
30
  ## Fields
29
31
 
@@ -32,17 +34,21 @@ Blume only reads `meta.ts` files from folders your content covers: one under a f
32
34
  | `title` | `string` | The group's label. Defaults to the humanized folder name. |
33
35
  | `icon` | `string` | Icon shown next to the label. |
34
36
  | `order` | `number` | Position among sibling groups and pages. Lower numbers sort first. |
35
- | `collapsed` | `boolean` | Under the [`group` display mode](/docs/content/navigation#display-modes), whether the group starts collapsed. |
37
+ | `collapsed` | `boolean` | Under the [`group` display mode](/docs/content/navigation#display-modes), whether the group starts collapsed. Unset, it starts collapsed unless it holds the current page or is the only row at the top of the sidebar. |
36
38
  | `display` | `"flat" \| "group" \| "page"` | Render mode for this group; overrides the global [`navigation.sidebar.display`](/docs/content/navigation#display-modes). |
37
39
  | `directory` | `"card" \| "accordion" \| "none"` | List the group's pages below its `index` page's content. Nested folders inherit it. See [directory listings](/docs/content/navigation#directory-listings). |
38
40
  | `pages` | `string[]` | Explicit order for the group's children, by slug. |
39
41
 
40
- The `pages` array lists children by slug — the folder or file name with its numeric prefix and any parentheses stripped (so `01-quickstart.mdx` is `"quickstart"`). Each listed child takes its position in the array as its order (`0`, `1`, `2`, …). Children you leave out still appear, sorted by their own order: an `index` page stays first, a child with a `sidebar.order`, a numeric prefix, or its own `meta.ts` `order` sorts by that number among the listed ones, and a child with none of these goes after them. List every child when the array should be the whole order.
42
+ The `pages` array lists children by slug — the folder or file name with its numeric prefix and any parentheses stripped (so `01-quickstart.mdx` is `"quickstart"`). Each listed child takes its position in the array as its order (`0`, `1`, `2`, …). Children you leave out still appear, sorted by their own order: an `index` page stays first, a child with a `sidebar.order`, a numeric prefix, or its own `meta.ts` `order` sorts by that number among the listed ones, and a child with none of these goes after them. List every child when the array should be the whole order. An entry that names no page or folder in the group orders nothing, so Blume warns about it as `BLUME_META_UNKNOWN_PAGE`, at its line in `meta.ts`, and suggests the slug it most likely means.
41
43
 
42
44
  The array orders pages among pages and groups among groups, but it doesn't interleave the two where loose pages list above groups: at the top of the sidebar (and of each [tab](/docs/content/navigation#tabs) section), and in any group with a [`flat`](/docs/content/navigation#display-modes) subgroup, whose header would otherwise seem to own the pages after it. There, `pages: ["advanced", "intro"]` still lists the `intro` page above the `advanced` group.
43
45
 
44
46
  How groups render — flat headers, collapsible disclosures, or drill-in panels — defaults to the sidebar-wide `navigation.sidebar.display`; set `display` here to override it for this group alone. A folder's `index` page can also set it from frontmatter, which wins over `meta.ts` — see [per-group overrides](/docs/content/navigation#per-group-overrides).
45
47
 
48
+ ## Generated groups
49
+
50
+ An [OpenAPI](/docs/references/openapi), [AsyncAPI](/docs/references/asyncapi), or [GraphQL](/docs/references/graphql) reference names and orders its tag groups from the spec. To change one, add a `meta.ts` in the folder the tag's pages publish under: with the reference at `/api`, `api/pets/meta.ts` in your content folder configures the **Pets** group. The fields it sets replace the generated ones, and the rest keep their generated values, so a `meta.ts` that sets only `title` keeps the tag's place in the spec's order.
51
+
46
52
  ## Computed meta
47
53
 
48
54
  Because `meta.ts` is a real module, you can compute the meta — pass a function (sync or `async`) instead of an object to build it at scan time. Handy for ordering pages from an external source:
@@ -46,7 +46,13 @@ export default defineMeta({
46
46
 
47
47
  See [Folder meta](/docs/content/meta) for every field and computing meta at scan time.
48
48
 
49
- A folder's `meta.title` and its own `index` page's frontmatter `title` are resolved independently — translating one under i18n and forgetting the other renders a correct sidebar with a stale `<title>`/heading on the landing page itself. Blume reports a `BLUME_NAV_INDEX_TITLE_MISMATCH` warning when they diverge on an index page that hides its own sidebar row (`sidebar.hidden: true`), where the folder title is the only sidebar label the page has. When the index row is visible, the sidebar already shows both titles, so pairing a folder title with a different page title ("CLI" over "Overview") is fine. Untranslated pages filled in from the fallback locale are exempt — their title belongs to the fallback locale, and the fix is translating the page, not editing its frontmatter.
49
+ A folder's `meta.title` and its own `index` page's frontmatter `title` are resolved independently — translating one under i18n and forgetting the other renders a correct sidebar with a stale heading on the landing page itself. Blume reports a `BLUME_NAV_INDEX_TITLE_MISMATCH` warning when they diverge on an index page that hides its own sidebar row (`sidebar.hidden: true`), where the folder title is the only sidebar label the page has. The check reads `title`, which the page's heading shows; `seo.title` replaces only the `<title>`, so it doesn't count. Untranslated pages filled in from the fallback locale are exempt — their title belongs to the fallback locale, and the fix is translating the page, not editing its frontmatter.
50
+
51
+ Like every warning, it fails [`blume validate --strict`](/docs/cli/validate). Any of these clears it:
52
+
53
+ - Change the page's `title` or the folder's `meta.title` so the two match.
54
+ - Keep the page's title on purpose by setting its `sidebar.label` to the folder title, the way any page sets a sidebar label that differs from its title.
55
+ - Show the index row again. The sidebar then shows both titles, so pairing a folder title with a different page title ("CLI" over "Overview") is fine.
50
56
 
51
57
  To group pages _without_ adding a URL segment, use a parenthesized folder name — see [Pages](/docs/content#group-folders).
52
58
 
@@ -63,7 +69,7 @@ navigation: {
63
69
  ```
64
70
 
65
71
  - **`flat`** (default) — a non-collapsible header with its pages listed beneath. Pages that aren't in any group always list first, above the group sections, so they can't be mistaken for a group's children.
66
- - **`group`** — a collapsible `<details>` disclosure per group. Groups start collapsed by default; a group containing the current page always starts open, so only the section you're in is expanded. Set `collapsed: false` in [folder meta](/docs/content/meta) to force a group open regardless.
72
+ - **`group`** — a collapsible `<details>` disclosure per group. Groups start collapsed by default; a group containing the current page always starts open, so only the section you're in is expanded. A group that is the only row at the top of the sidebar, like a folder that wraps every page, starts open too, so the sidebar never shows a single closed row. Set `collapsed: false` in [folder meta](/docs/content/meta) to force a group open regardless, or `collapsed: true` to keep that lone group closed.
67
73
  - **`page`** — each group is a single row whose chevron slides the sidebar into a sub-panel showing only that group's items, with a back arrow at the top. A group with an index page links the row's name to that page, and a group without one slides the panel in from anywhere on the row. The panel is route-aware, so landing directly on a page inside the group opens straight to it. After the back arrow, the row of the section you're in stays highlighted.
68
74
 
69
75
  :::tip
@@ -152,7 +158,9 @@ sidebar:
152
158
  hidden: true
153
159
  ```
154
160
 
155
- A folder's `index` page appears both as the group row's link and as the first row inside the group. Hide the index page to keep only the linked header: the group row still opens the landing page, and previous/next links still pass through it.
161
+ A hidden page also stays out of the sitemap and `llms.txt`, and out of site search unless [`search.indexing.includeHiddenPages`](/docs/configuration/search#excluding-pages) is on.
162
+
163
+ A folder's `index` page appears both as the group row's link and as the first row inside the group. Hide the index page to keep only the linked header: the group row still opens the landing page, and previous/next links still pass through it. Since the navigation still links the page, it isn't a hidden page anywhere else: it stays in the sitemap, site search, and `llms.txt`. The same goes for the site's home page, the root `index` page: hiding it drops its sidebar row, but the site's root URL still serves it, so it stays listed too.
156
164
 
157
165
  ## Tabs
158
166
 
@@ -180,7 +188,7 @@ navigation: {
180
188
  }
181
189
  ```
182
190
 
183
- A tab's `path` is its section prefix, and it doubles as the link target. A section whose `path` isn't a page of its own — a folder with no `index.mdx` — would link to a 404, so the tab falls back to the first page in the section instead. A static [custom page](/docs/advanced/custom-pages) at the tab's `path` counts as the section's own page: with `pages/guides.astro`, a `/guides` tab lands on that page while the `guides/` folder fills its sidebar. So does the generated [changelog](/docs/advanced/changelog) index, so a `/changelog` tab opens the timeline rather than the newest entry.
191
+ A tab's `path` is its section prefix, and it doubles as the link target. A section whose `path` isn't a page of its own — a folder with no `index.mdx` — would link to a 404, so the tab falls back to the first page in the section instead. That includes a tab at `path: "/"` on a site with no root `index.mdx`: it links to the first page in the sidebar. A static [custom page](/docs/advanced/custom-pages) at the tab's `path` counts as the section's own page: with `pages/guides.astro`, a `/guides` tab lands on that page while the `guides/` folder fills its sidebar. So does the generated [changelog](/docs/advanced/changelog) index, so a `/changelog` tab opens the timeline rather than the newest entry.
184
192
 
185
193
  Set `href` when you want a tab to land somewhere else — a particular page in the section, say:
186
194
 
@@ -267,7 +275,7 @@ navigation: {
267
275
  }
268
276
  ```
269
277
 
270
- Each link takes a `label`, an `href`, and an optional `icon` (a [built-in icon](/docs/content/components#icon) name, image path/URL, or inline SVG — the same as anywhere else). An `href` may point anywhere: an external URL opens in a new tab, while an internal route (`/contact`) is validated against your pages at build time and by [`blume doctor`](/docs/cli/doctor), warning you if nothing matches.
278
+ Each link takes a `label`, an `href`, and an optional `icon` (a [built-in icon](/docs/content/components#icon) name, image path/URL, or inline SVG — the same as anywhere else). An `href` may point anywhere: an external URL opens in a new tab, while an internal route (`/contact`) is validated against your pages at build time and by [`blume doctor`](/docs/cli/doctor), warning you if nothing matches. A path to a file in `public/` (`/spec.pdf`) or one Blume generates (`/llms.txt`) matches too.
271
279
 
272
280
  ## Explicit sidebar
273
281
 
@@ -347,7 +355,7 @@ A right-rail outline is generated automatically from each page's headings — `#
347
355
 
348
356
  Below the table of contents, every page shows a set of quick actions. In windows at least 1,280px wide they sit in the right-hand column with the outline. In narrower windows and on phones they sit at the bottom of the “On this page” dropdown above the content, which shows on a page with no headings too:
349
357
 
350
- - **Edit on GitHub** — links straight to the source file. Appears once you set [`github`](/docs/configuration) in your config.
358
+ - **Edit on GitHub** — links straight to the source file. Appears once you set [`github`](/docs/configuration) in your config, on every page whose file is in the repository: a file git ignores, like a generated API reference written into a gitignored folder, gets no edit link, since GitHub has no file to open.
351
359
  - **Scroll to top** — smoothly returns to the top of long pages. It's in the right-hand column only, since the dropdown already sits at the top of the page.
352
360
 
353
361
  Others hand the page to AI tools — **Copy as Markdown** and **Open in chat** — covered in [Markdown for agents](/docs/discoverability/markdown#copy-as-markdown).
@@ -32,7 +32,7 @@ Locale directories and version snapshots inside the vault are read the same way
32
32
 
33
33
  A link to an `index` note lands on its folder's route rather than a phantom `/index`. **An unresolved wikilink degrades to plain text with a build warning instead of failing the build**, so a vault mid-refactor still publishes. Single-line `%%comments%%` are stripped, a wikilink inside an HTML comment (`<!-- [[Draft]] -->`) is left alone since Obsidian hides it too, and a note with no `title` in its frontmatter is titled by its filename — the same rule Obsidian itself applies. An `index` note is the one exception: it names a route rather than a note, so its title falls through to Blume's usual derivation (first heading, then the humanized segment). Fenced, indented, and inline code passes through verbatim, so a note documenting the syntax survives.
34
34
 
35
- Dot-folders are skipped, including Obsidian's own `.obsidian` config directory and `.trash` — which the dev watcher also ignores, so moving a pane in the app doesn't rebuild your site. Editing a note does, and so does deleting one to the trash, since the note leaves the vault. The directories no content scan reads (`node_modules`, `dist`, `.git`, …) are skipped too, so a vault rooted at the project itself doesn't publish dependency READMEs. A note with `#` or `?` in its path is left out with an error, the way a [content file](/docs/content#files-and-routes) is: Astro can't load its copy, so rename it. Symlinks inside the vault are followed, the way the filesystem source follows them, so a shared folder linked into the vault publishes with it. A vault that sits inside the filesystem source's root must be excluded from it (`filesystem({ root: "docs", exclude: ["**/_*", "**/.*", "vault/**"] })` — an `exclude` replaces the default `["**/_*", "**/.*"]` rather than adding to it, so keep those two to leave `_`-prefixed partials and dot-files unpublished); [`blume version <id>`](/docs/cli/version) then leaves it out of the snapshot, since the vault keeps publishing its own notes as current.
35
+ Dot-folders are skipped, including Obsidian's own `.obsidian` config directory and `.trash` — which the dev watcher also ignores, so moving a pane in the app doesn't rebuild your site. Editing a note does, and so does deleting one to the trash, since the note leaves the vault. The directories no content scan reads (`node_modules`, `dist`, `.git`, …) are skipped too, so a vault rooted at the project itself doesn't publish dependency READMEs. A note with `#` or `?` in its path is left out with an error, the way a [content file](/docs/content#files-and-routes) is: Astro can't load its copy, so rename it. Symlinks inside the vault are followed, the way the filesystem source follows them, so a shared folder linked into the vault publishes with it. A vault that sits inside the filesystem source's root must be excluded from it (`filesystem({ root: "docs", exclude: ["vault/**"] })`); [`blume version <id>`](/docs/cli/version) then leaves it out of the snapshot, since the vault keeps publishing its own notes as current.
36
36
 
37
37
  Not yet lowered: callouts (`> [!note]`) render as plain blockquotes, embeds (`![[image.png]]`) pass through untouched, multi-line `%%comments%%` are left in place, and there is no backlink graph.
38
38
 
@@ -37,7 +37,9 @@ mdxRemote({
37
37
 
38
38
  A relative link between remote files (`[Errors](./02-errors.mdx)`) lands on the page the linked file publishes at, with its ordering prefix dropped from the route, and `blume validate` checks it the same way.
39
39
 
40
- A private repo's token is read from the `GITHUB_TOKEN` environment variable — the adapter declares it, so `blume dev` and `blume build` warn when it's unset. It is never inlined into your config or generated output, and it is only ever sent to GitHub's own hosts (`api.github.com`, `raw.githubusercontent.com`), never to a custom `url` base. A public repo works without it.
40
+ A private repo's token is read from the `GITHUB_TOKEN` environment variable. It is never inlined into your config or generated output, and it is only ever sent to GitHub's own hosts (`api.github.com`, `raw.githubusercontent.com`), never to a custom `url` base. A public repo works without it. With `github`, the adapter declares the variable, so `blume dev` and `blume build` warn `BLUME_MISSING_SECRET` when it's unset: the file listing goes through GitHub's API, which allows 60 unauthenticated requests an hour. A raw `url` reads files only, so nothing warns up front; when GitHub refuses a file with a 401, 403, or 404 while `GITHUB_TOKEN` is unset, the warning for the skipped file says the variable isn't set.
41
+
42
+ A file written to read on GitHub often opens with a `# Heading`, while Blume renders the page's `title` as its `<h1>`. When a remote page's first line is an h1 that matches its front matter `title`, or the front matter sets no `title`, the heading is dropped and its text is the title, so the page shows one `<h1>`. A first heading that differs from `title` stays.
41
43
 
42
44
  Remote pages are rendered with full MDX-plus-component fidelity: their bodies are materialized into a hidden staging directory and rendered through Astro alongside your local docs, so callouts, tabs, and every other Blume component keep working.
43
45
 
@@ -7,7 +7,7 @@ Blume renders standard Markdown and MDX with a curated, GitHub-flavored feature
7
7
 
8
8
  ## Headings
9
9
 
10
- Structure a page with headings. Blume renders your frontmatter `title` as the page heading, so start your content at `##` — `##` and `###` become entries in the table of contents. Every `##`–`######` heading is also wrapped in a link to its own anchor, so readers can click a heading to copy, bookmark, or share a permalink straight to that section (hover to reveal the `#`). Turn this off with `markdown: { headingAnchors: false }` in `blume.config.ts`.
10
+ Structure a page with headings. Blume renders your frontmatter `title` as the page heading, so start your content at `##` — `##` and `###` become entries in the table of contents. A page without a `title` takes its first `#` heading as its title, and when the page opens with that heading, it shows once, as the page heading, which keeps the heading's anchor so links to it still land. `llms-full.txt` lists it once too. Every `##`–`######` heading is also wrapped in a link to its own anchor, so readers can click a heading to copy, bookmark, or share a permalink straight to that section (hover to reveal the `#`). Turn this off with `markdown: { headingAnchors: false }` in `blume.config.ts`.
11
11
 
12
12
  ```md
13
13
  ## Section
@@ -19,21 +19,27 @@ Structure a page with headings. Blume renders your frontmatter `title` as the pa
19
19
 
20
20
  ### Custom anchors
21
21
 
22
- Anchor ids are generated from the heading text, so a reworded heading gets a new anchor. Append `[#custom-id]` to pin the anchor instead — the marker never renders, and links keep working no matter how the heading reads. Pinned anchors also stay identical across [translated locales](/docs/content/i18n), where auto-generated ids would otherwise differ per language.
22
+ Anchor ids are generated from the heading text, so a reworded heading gets a new anchor. A `<Badge>` in a heading is a label, not part of that text: `## Install <Badge>beta</Badge>` anchors as `#install` and reads "Install" in the table of contents. An id never ends in a dash, so `## Features ✨` anchors as `#features`, and a component or image at either end of a heading adds no dash. Append `[#custom-id]` to pin the anchor instead — the marker never renders, and links keep working no matter how the heading reads. Pinned anchors also stay identical across [translated locales](/docs/content/i18n), where auto-generated ids would otherwise differ per language.
23
23
 
24
24
  ```md
25
25
  ## Getting started [#setup]
26
26
  ```
27
27
 
28
- Link to it as `/page#setup`. The syntax matches Fumadocs, so migrated content works verbatim.
28
+ Link to it as `/page#setup`. The syntax matches Fumadocs, so migrated content works verbatim. The id is used exactly as written: smart punctuation turns `--` into a dash and curls quotes in the heading's text, but not in the marker, so `[#read--write]` anchors as `#read--write`.
29
29
 
30
- The `{#custom-id}` form used by Pandoc, kramdown, and Markdown-based specification toolchains is accepted as an equivalent in `.md` files. Only that unspaced spelling pins an anchor: the spaced `{ #custom-id }` and kramdown's `{: #custom-id }` (both from MkDocs' `attr_list`) stay in the heading text. In `.mdx`, a bare `{…}` is a JSX expression and the page fails to compile — `blume check` reports all three spellings as `BLUME_MDX_CURLY_ANCHOR` — so write `[#custom-id]` there, or escape the braces. The escaped form pins the same anchor in both formats, which makes it the right spelling for a partial that `.mdx` pages [include](/docs/content/includes):
30
+ The `{#custom-id}` form used by Pandoc, kramdown, and Markdown-based specification toolchains is accepted as an equivalent in `.md` files. Only that unspaced spelling pins an anchor: the spaced `{ #custom-id }` and kramdown's `{: #custom-id }` (both from MkDocs' `attr_list`), and an attribute list that sets the id beside classes or attributes (`{ #custom-id .wide }`), stay in the heading's text and its anchor, and `blume check` warns about them in `.md` as `BLUME_MD_CURLY_ANCHOR`. In `.mdx`, a bare `{…}` is a JSX expression and the page fails to compile — `blume check` reports all three spellings as `BLUME_MDX_CURLY_ANCHOR` — so write `[#custom-id]` there, or escape the braces. The escaped form pins the same anchor in both formats, which makes it the right spelling for a partial that `.mdx` pages [include](/docs/content/includes):
31
31
 
32
32
  ```md
33
33
  ## Getting started \{#setup\}
34
34
  ```
35
35
 
36
- Fragment links can also target a raw HTML element with an `id` (`<a id="setup"></a>`); `blume validate` accepts those alongside heading anchors.
36
+ Fragment links can also target a raw HTML element with an `id` (`<a id="setup"></a>`), or an `<a name="setup">`; `blume validate` accepts those alongside heading anchors.
37
+
38
+ An empty `<a>` inside a heading is the heading's own anchor, the way GitBook exports (`<a href="#setup" id="setup"></a>`) and wikis (`<a name="setup"></a>`) write one. Blume takes it out of the heading and gives the heading its `id`, or else its `name`, so `#setup` links keep landing there and the heading keeps its own self-link. A `[#custom-id]` marker on the same heading wins.
39
+
40
+ ```md
41
+ ## Getting started <a id="setup"></a>
42
+ ```
37
43
 
38
44
  ### Table of contents markers
39
45
 
@@ -150,6 +156,10 @@ Link to other pages or external sites. Images accept a relative path to a file n
150
156
 
151
157
  A relative page link (`./install`, `../guides/setup`) resolves from the page's own folder — on an index page, the folder it introduces — and a link to a Markdown file (`./setup.md`, `../intro.mdx`) lands on the page that file publishes, its `slug` included. A dotted page name (`./node.js` for a `node.js.mdx` page) counts as a page link when a page publishes there, and a component's string `href` (`<Card href="./install">`) resolves the same way. Blume writes each one as the root-relative route in the built page, so links authored for GitHub or Docusaurus keep working, and [`blume validate`](/docs/cli/validate) checks them the same way.
152
158
 
159
+ A root-relative link to a Markdown file (`/guides/setup.md`, the way VitePress and Docsify write one) lands on that file's page too, read from your content root; under a [deployment `base`](/docs/deployment#subpath-deploys), one that starts with the base is read as including it. One that names no file in your content keeps its path, since `/guides/setup.md` is also the URL of that page's [Markdown copy](/docs/discoverability/markdown#raw-markdown). To link a page's Markdown copy whatever its file is called, write a raw `<a href="/guides/setup.md">`, which keeps its path, or the copy's full URL.
160
+
161
+ A link to a Markdown file that doesn't exist tries the same file with the other extension first. So after you rename `setup.md` to `setup.mdx`, a link to `./setup.md` still lands on that file's page, its `slug` and ordering prefix included, and `blume validate` passes it. A link that names neither file falls back to a plain relative link with the extension dropped. Either way, the link no longer names a real file, so it breaks wherever the Markdown is read as files, such as on GitHub. Update the extension in your links when you rename a file.
162
+
153
163
  Read the [quickstart](/docs/quickstart) to get started.
154
164
 
155
165
  ```md
@@ -166,6 +176,10 @@ External links open in the same tab by default. Set `markdown: { externalLinks:
166
176
  ![Build output](./images/build-output.png)
167
177
  ```
168
178
 
179
+ An SVG whose size Astro can't read is served as it is instead of failing the build. Astro reads an SVG's size from its `<svg>` tag, which needs a width and height or a viewBox and has to end within the file's first 1,000 bytes; a draw.io export puts its whole diagram in a `content` attribute on that tag, which pushes it past them. Blume warns about each one as `BLUME_SVG_UNOPTIMIZED` at the line that embeds it. The image still shows, without the intrinsic size an optimized image carries; export the diagram without the copy of its source, or remove the `content` attribute, to have it optimized.
180
+
181
+ A link to a file next to your content that isn't a page (`[the spec](./spec.pdf)`, `[data](../files/data.csv)`, or a reference definition like `[spec]: ./spec.pdf`) publishes that file with the page, and the built link points at the published copy, keeping a suffix like `#page=2`. So does an HTML element that names one: the `src` of an `<img>`, `<video>`, `<source>`, or `<audio>`, and the `href` of an `<a>` or of a component like `<Card>`, as raw HTML in `.md` or an element in `.mdx`. These copies are served as they are, without the optimization an image embed gets. A relative link that names no file next to the page keeps its path, so one to a file under `public/` still works.
182
+
169
183
  Absolute paths under `public/` (`![Alt text](/screenshot.png)`) are served verbatim with no optimization — use them for files that must keep their exact bytes and URL, like a logo referenced from outside your docs. Remote images are also passed through untouched unless their host is authorized in the [`image` config](/docs/configuration#images).
170
184
 
171
185
  Content images are click-to-zoom by default — readers can click any image to open it in a lightbox. Turn this off with `markdown: { imageZoom: false }` in `blume.config.ts`, or opt a single image out with `data-no-zoom`.
@@ -202,6 +216,20 @@ export default defineConfig({
202
216
  ```
203
217
  ````
204
218
 
219
+ The title is every word after the language, so ` ```js Install the client ` titles the block "Install the client". Keywords like `lineNumbers` and `wrap`, line ranges like `{1,4-5}`, and `key="value"` options aren't part of it, and `title="..."` sets the title explicitly. A title in brackets, like ` ```ts [blume.config.ts] `, shows without them, and a line range written against the brackets, as in ` ```ts [blume.config.ts]{2} `, still highlights its lines.
220
+
221
+ The language is any [Shiki language](https://shiki.style/languages) id or alias, in any case: ` ```JSON ` highlights like ` ```json `. A block in a language Shiki doesn't know renders as plain text, and Blume warns about its fence as `BLUME_UNKNOWN_CODE_LANGUAGE`; write `text` for a block that's plain on purpose.
222
+
223
+ Options other docs tools read do nothing here, and Blume warns about each as `BLUME_CODE_FENCE_OPTION` with the spelling to use instead: MkDocs' `hl_lines="2 3"` (a `{2,3}` range here) and `linenums="1"` (`lineNumbers`), `showLineNumbers` (`lineNumbers`), Mintlify's `lines` (`lineNumbers`), Fern's `wordWrap` (`wrap`), and `filename="app.js"` (`title="app.js"`). On a Rust block, rustdoc's and mdBook's `ignore`, `no_run`, `should_panic`, `compile_fail`, `edition2015` through `edition2024`, `noplayground`, and `editable` warn too: Blume never compiles, tests, or runs a code block, so remove them. None of them becomes part of the title.
224
+
225
+ A `console` (or `shellsession`) block is a terminal session: commands after a prompt like `$`, with their output. Its copy button copies only the commands, without their prompts, so the copy pastes straight into a terminal. A command that ends in `\` keeps its continuation lines.
226
+
227
+ ```console
228
+ $ npm install blume
229
+ added 1 package in 2s
230
+ $ npx blume dev
231
+ ```
232
+
205
233
  Inline code can be highlighted too: add a `{:lang}` marker inside a backtick span and it's colored like a tiny code block — `useState(){:js}` or `T extends object{:ts}`. It only kicks in when you add the marker, so plain inline code stays untouched — nothing to switch on.
206
234
 
207
235
  Highlighting uses the `github-light`/`github-dark` themes by default. Swap in any [bundled Shiki theme](https://shiki.style/themes) per color mode with `markdown.code.theme` — it colors every code surface at once (fences, inline snippets, `<CodeBlock>`, and `<Diff>`):
@@ -309,7 +337,7 @@ Both work alongside a title and `lineNumbers`. To wrap every block, set `markdow
309
337
 
310
338
  ### Highlighting
311
339
 
312
- Annotate code with GitHub-style comments to draw attention to lines, words, and changes. The comments are stripped from the rendered output, so the code stays copy-paste clean. All four are on by default — no configuration.
340
+ Annotate code with GitHub-style comments to draw attention to lines, words, and changes. The comments are stripped from the rendered output, so the code stays copy-paste clean. All of them are on by default — no configuration.
313
341
 
314
342
  Mark a line with `// [!code highlight]` to give it a highlighted background:
315
343
 
@@ -336,6 +364,13 @@ import { createServer } from "node:http"; // [!code word:createServer]
336
364
  createServer().listen(3000);
337
365
  ```
338
366
 
367
+ Flag a line as a problem with `// [!code error]` or `// [!code warning]`, tinted red and amber:
368
+
369
+ ```ts
370
+ const port = process.env.PORT; // [!code warning]
371
+ server.listen(port.trim()); // [!code error]
372
+ ```
373
+
339
374
  Dim everything except the lines you mark with `// [!code focus]` (the rest sharpens on hover):
340
375
 
341
376
  ```ts
@@ -345,7 +380,7 @@ export default defineConfig({
345
380
  });
346
381
  ```
347
382
 
348
- Or highlight lines by **number** instead of comments — useful when you can't edit the code. Put a brace range after the language; single lines, comma lists, and `start-end` spans all work:
383
+ Or highlight lines by **number** instead of comments — useful when you can't edit the code. Put a brace range after the language, with or without a space between them (` ```ts{1,4-5} ` works too); single lines, comma lists, and `start-end` spans all work:
349
384
 
350
385
  ```ts {1,4-5}
351
386
  import { defineConfig } from "blume";
@@ -580,7 +615,7 @@ timeline
580
615
 
581
616
  ## Callouts
582
617
 
583
- Callouts pull a reader's attention to context, advice, or risk. Write them as `:::type` directives; add a title in brackets, like `:::warning[Heads up]`. Directives are an MDX-only feature — in a `.md` file a `:::note` line stays literal text.
618
+ Callouts pull a reader's attention to context, advice, or risk. Write them as `:::type` directives; add a title in brackets, like `:::warning[Heads up]`. The title keeps its inline formatting, so code, emphasis, and links in the brackets render in the title. Directives are an MDX-only feature — in a `.md` file a `:::note` line stays literal text, and `blume dev`, `blume build`, and `blume check` warn about it as `BLUME_MD_DIRECTIVE`. Rename the page to `.mdx` to render it as a callout.
584
619
 
585
620
  ### Note
586
621
 
@@ -682,6 +717,28 @@ Commit `blume.config.ts`, not `.blume/`.
682
717
  ::::
683
718
  ```
684
719
 
720
+ Write the name right after the colons. `::: tip`, with a space (the spelling VitePress, VuePress, and Docusaurus v2 use), isn't a directive, so the page shows the line as text, and Blume warns about a callout name written that way as `BLUME_DIRECTIVE_SPACED_NAME`. A title goes in brackets: text after the name, as in `:::tip Some title`, opens the callout but never shows, and Blume warns about it as `BLUME_DIRECTIVE_OPENING_TEXT` with the bracketed spelling, `:::tip[Some title]`. Inside a container, a closing fence with text after its colons still closes it, and the text is dropped: a `::: card` line inside a `:::warning` ends the callout there, and `card` never shows. Blume warns about that line as `BLUME_DIRECTIVE_CLOSING_TEXT`. To keep it inside the callout as text, give the callout a longer fence, as above, or remove the text after the colons.
721
+
722
+ ### GitHub alerts
723
+
724
+ GitHub's alert syntax renders as a callout too: a quote whose first line is `[!NOTE]`, `[!TIP]`, `[!IMPORTANT]`, `[!WARNING]`, or `[!CAUTION]`, in any case. Text after the marker on that line becomes the title.
725
+
726
+ {/* prettier-ignore */}
727
+ > [!TIP]
728
+ > Set `deployment.site` so sitemaps and Open Graph images use absolute URLs.
729
+
730
+ {/* prettier-ignore */}
731
+ ```md
732
+ > [!TIP]
733
+ > Set `deployment.site` so sitemaps and Open Graph images use absolute URLs.
734
+ ```
735
+
736
+ `[!NOTE]` and `[!TIP]` render as the callouts of the same name, `[!IMPORTANT]` as a note, `[!WARNING]` as a warning, and `[!CAUTION]` as a danger callout, matching GitHub's red. As on GitHub, an alert quoted inside another stays a quote, and so does any other marker, such as `[!INFO]`.
737
+
738
+ A formatter that joins lines, like oxfmt with `proseWrap: "never"`, pulls the first body line up beside the marker, where it becomes the title (it [collapses directives](/docs/faq#why-is-oxfmt--ultracite-collapsing-my-directives) the same way). Leave a `>` line blank after the marker to keep them apart; GitHub renders that form too.
739
+
740
+ Alerts are MDX-only, like directives. In a `.md` file the quote stays a quote and shows its marker as text, and `blume dev`, `blume build`, and `blume check` warn about it as `BLUME_MD_GITHUB_ALERT`. Rename the page to `.mdx` to render it as a callout.
741
+
685
742
  ## Math
686
743
 
687
744
  Render LaTeX with KaTeX, useful for math-heavy or scientific docs. Put `$$…$$` on lines of its own for a centered block:
@@ -715,3 +772,14 @@ Blume converts straight quotes and dashes to typographic equivalents as you writ
715
772
  ```md
716
773
  "Quotes" become curly, -- becomes an en dash, --- an em dash, and ... an ellipsis.
717
774
  ```
775
+
776
+ ## Syntax from other tools
777
+
778
+ Blume reads the syntax on this page and nothing else. Syntax another docs tool reads shows on the page as written, or breaks an `.mdx` page when it renders, so `blume dev`, `blume build`, and `blume check` warn about it at its line. Code blocks, inline code, and comments are skipped, so wrap an example in inline code to show it as text.
779
+
780
+ - `BLUME_TEMPLATE_TAG`: a Liquid or Markdoc tag (`{% include note.html %}`, `{% /callout %}`) or a Liquid output (`{{ site.title }}`) in a `.md` page. Blume runs no template language, so replace the tag with what it produced, and a site-wide value with a [variable](/docs/content/variables). A `{{name}}` that could be a variable isn't reported here.
781
+ - `BLUME_WIKILINK_UNSUPPORTED`: a wiki link (`[[Home]]`, `[[Text|Page]]`) that names one of the site's pages by its file name or title, or any wiki link with a `|`. Only an [Obsidian vault](/docs/content/sources/obsidian) source reads wiki links. The warning gives the Markdown link that replaces it.
782
+ - `BLUME_MDC_SYNTAX`: a Nuxt Content (MDC) block component (`::callout` … `::`) or inline component (`:badge[New]{color="primary"}`). Use the matching [component](/docs/content/components) or a `:::` callout instead.
783
+ - `BLUME_MDX_ATTRIBUTE_LIST`: an attribute list (`{ width="300" }`, `{: .note }`) in an `.mdx` page. MDX reads the braces as JavaScript, so the page fails to build. Set the attributes on a JSX element instead (`<img src="…" width="300" />`), and pin a heading's anchor with `[#id]`.
784
+ - `BLUME_MD_ATTRIBUTE_LIST`: an attribute list in a `.md` page, like kramdown's `{: .note }` or `{: #intro }` on its own line or after a block, or `{ width="300" }` after an image. The page shows it as written. Set the attributes on an HTML element instead (`<img src="…" width="300">`), and pin a heading's anchor with `[#id]`. A heading's `{#id}` marker is covered by [`BLUME_MD_CURLY_ANCHOR`](#custom-anchors) instead.
785
+ - `BLUME_MDX_UNCLOSED_ELEMENT`: an HTML void element without a closing slash (`<img …>`, `<br>`) in an `.mdx` page, or in a partial an `.mdx` page [includes](/docs/content/includes). MDX reads it as an unclosed JSX element, so close it: `<br />`.
@@ -38,4 +38,6 @@ A `{{name}}` that isn't defined in prose is a build error, `BLUME_UNDEFINED_VARI
38
38
 
39
39
  To show a reference in prose without defining it, put it in inline code: `` `{{name}}` ``.
40
40
 
41
- A site that defines no variables is unaffected: `{{…}}` in its pages stays as written.
41
+ A site that defines no variables is unaffected: `{{…}}` in its `.md` pages stays as written. In `.mdx`, braces are a JavaScript expression, so an undefined `{{name}}` fails the page when it renders, reported as [`BLUME_MDX_UNDEFINED_NAME`](/docs/content#markdown-and-mdx) at its line. Escape both opening braces (`\{\{name}}`) or put it in inline code.
42
+
43
+ A `{{…}}` that holds more than a name, like Liquid's `{{ site.title }}` or `{{ page.url | relative_url }}`, is never a variable. Blume warns about one in a `.md` page as `BLUME_TEMPLATE_TAG`, since the page would show it as written.
@@ -235,7 +235,7 @@ Every build writes an [agent skill](https://agentskills.io) for your docs, a `SK
235
235
  - the site's title and description, and when an agent should use the skill
236
236
  - how to read any page as Markdown, plus `llms.txt`, `llms-full.txt`, the [MCP server](/docs/discoverability/mcp), the JSON API, and the changelog, when the site serves them
237
237
  - your API references
238
- - a map of the docs in sidebar order, linking each page's Markdown with its one-line description
238
+ - a map of the docs in sidebar order, linking each page's Markdown with its one-line description (its meta description, as in `llms.txt`)
239
239
 
240
240
  The skill is named after your site's `title` (`Acme Docs` becomes `acme-docs`). It covers the current version in the default language, leaves out API operation pages and changelog entries (each has one line of its own above the map), skips the pages `llms.txt` does (drafts, hidden and `noindex` pages, and pages with [`ai.exclude`](/docs/discoverability/llms-txt#excluding-a-page)), and lists up to 200 pages, pointing at `llms.txt` for the rest. The links are absolute, so the skill needs [`deployment.site`](/docs/deployment); a build without one skips it.
241
241
 
@@ -47,7 +47,7 @@ Errors are [RFC 9457](https://www.rfc-editor.org/rfc/rfc9457) problem details (`
47
47
 
48
48
  The **OpenAPI document** at `/openapi.json` is generated per build from your config, so it describes only what the deployed site serves: every JSON endpoint with a unique `operationId`, typed parameters, and response schemas, plus the text surfaces alongside — the [`.md` mirrors](/docs/discoverability/markdown), [`llms.txt`](/docs/discoverability/llms-txt) and `llms-full.txt`, [`agent-readability.json`](/docs/discoverability/agent-discovery#agent-readability) — and the [MCP endpoint](/docs/discoverability/mcp) when it's enabled. Frameworks that build tools from an OpenAPI description get the same reach an MCP client has. The document is linked from the [API catalog](/docs/discoverability/agent-discovery#api-catalog), the [readability manifest](/docs/discoverability/agent-discovery#agent-readability), the homepage `Link` header as `rel="service-desc"`, and `llms.txt`.
49
49
 
50
- None of this touches your own [API reference](/docs/references/openapi): a documented spec is rendered into pages, never served at `/openapi.json`, and the catalog lists both. A `public/openapi.json` you ship yourself takes over that route (the JSON endpoints stay). The `/api/…` catch-all steps aside when a docs section is served from the `/api` namespace (`content/api/overview.md`) or a custom page owns a rest route under `/api/`, so those pages keep winning.
50
+ None of this touches your own [API reference](/docs/references/openapi): a documented spec is rendered into pages, never served at `/openapi.json`, and the catalog lists both. A `public/openapi.json` you ship yourself takes over that route (the JSON endpoints stay), with a `BLUME_PUBLIC_OPENAPI_JSON` warning: the catalog still lists `/openapi.json` as the docs API's description, so agents would read your file as that. To publish a spec file of your own, put it at another path, like `public/specs/openapi.json`. The `/api/…` catch-all steps aside when a docs section is served from the `/api` namespace (`content/api/overview.md`) or a custom page owns a rest route under `/api/`, so those pages keep winning.
51
51
 
52
52
  ## Turning it off
53
53
 
@@ -13,7 +13,7 @@ agents: {
13
13
 
14
14
  While enabled, `blume build` writes two files to the root of your site:
15
15
 
16
- - **`/llms.txt`** — a compact index: your site title and description, then a linked list of every page with its summary, organized into sections that mirror your sidebar — folders and groups become headings, so an agent sees the docs' structure, not one flat blob.
16
+ - **`/llms.txt`** — a compact index: your site title and description, then a linked list of every page with its summary (its meta description: `seo.description` when the page sets one, else `description`), organized into sections that mirror your sidebar — folders and groups become headings, so an agent sees the docs' structure, not one flat blob.
17
17
  - **`/llms-full.txt`** — the entire corpus: each page's full Markdown body, with its source URL, in one file.
18
18
 
19
19
  Drafts, pages hidden from the sidebar (`sidebar.hidden`, or the top-level `hidden` shorthand), and `noindex` pages are excluded — except generated API reference pages, whose place in both files is set by `openapi` below rather than by `noindex`. Set [`deployment.site`](/docs/deployment) so the links and source URLs resolve to absolute addresses.
@@ -17,9 +17,9 @@ Append `.md` or `.mdx` to any page's URL to fetch its raw Markdown source — pe
17
17
 
18
18
  Nested routes work the same way (`/content/syntax.md`), and the home page is served at `/index.md`.
19
19
 
20
- The `.md` variant _downlevels_ components to plain Markdown for consumers that can't interpret JSX: `<TypeTable>` becomes a Markdown table, `<Callout>` a labeled blockquote, `<Steps>` an ordered list, `<Tabs>` bold-labeled sections, `<Card>` its title as a link over its body (and `<CardGroup>` the cards it holds), `<Accordion>` bold questions over their answers, `<FileTree>` its list, `<CodeGroup>` its titled code blocks, `<YouTube>` a link, and every other built-in component — Columns, Frame, Expandable, Badge, Tooltip, and the rest — its readable content. `<AutoTypeTable>`, which needs the type checker, stays as written, as do a `<Diff>` that reads its sides from files (`src`, or `before` and `after`) and a `<GithubInfo>` without `owner` and `repo`. The components a generated [API reference](/docs/references/openapi) page is made of downlevel too: `<Operation>` becomes the endpoint in its spec's own notation (`GET /pets/{id}`, `SEND user/signup`, `query pets`) with a deprecation marker, `<ApiTagOperations>` a list of those endpoints linked to their pages with their summaries, and `<ApiOverview>` the API's version and base URLs — so an agent reading a reference page knows what to call, and site search matches an endpoint's path. Props are read as literal data and never executed — strings, numbers, booleans, arrays, objects, and template strings, plus references to the page's `frontmatter`, so a prop like `title={frontmatter.status}` resolves to the same value the rendered page shows. Only props resolve: an expression in the page's own text, like `Owned by {frontmatter.owner}`, stays as written in the Markdown copy. For a value agents should read too, write it into the text, or, when it's the same on every page, make it a [content variable](/docs/content/variables), which every surface replaces. Anything that can't be converted faithfully — a custom component, or a prop computed by a function call or from an import — is left as-is, and component markup inside code, fenced or inline, is never touched. The same conversion applies to [`llms-full.txt`](/docs/discoverability/llms-txt) and the [MCP server](/docs/discoverability/mcp)'s `get_page` tool, so every agent-facing surface reads clean Markdown.
20
+ The `.md` variant _downlevels_ components to plain Markdown for consumers that can't interpret JSX: `<TypeTable>` becomes a Markdown table, `<Callout>` a labeled blockquote, `<Steps>` an ordered list, `<Tabs>` bold-labeled sections, `<Card>` its title as a link over its body (and `<CardGroup>` the cards it holds), `<Accordion>` bold questions over their answers, `<FileTree>` its list, `<CodeGroup>` its titled code blocks, `<YouTube>` a link, and every other built-in component — Columns, Frame, Expandable, Badge, Tooltip, and the rest — its readable content. `<AutoTypeTable>`, which needs the type checker, stays as written, as do a `<Diff>` that reads its sides from files (`src`, or `before` and `after`) and a `<GithubInfo>` without `owner` and `repo`. The components a generated [API reference](/docs/references/openapi) page is made of downlevel too: `<Operation>` becomes the endpoint in its spec's own notation (`GET /pets/{id}`, `SEND user/signup`, `query pets`) with a deprecation marker, and for an OpenAPI operation, its request body's properties and example and its responses with their examples, `<ApiTagOperations>` a list of those endpoints linked to their pages with their summaries, and `<ApiOverview>` the API's version and base URLs — so an agent reading a reference page knows what to call, and site search matches an endpoint's path. [`<Changelog />`](/docs/advanced/changelog#writing-your-own-index) becomes the release list it renders: year headings over a linked row per release. Props are read as literal data and never executed — strings, numbers, booleans, arrays, objects, and template strings, plus references to the page's `frontmatter`, so a prop like `title={frontmatter.status}` resolves to the same value the rendered page shows. Only props resolve: an expression in the page's own text, like `Owned by {frontmatter.owner}`, stays as written in the Markdown copy. For a value agents should read too, write it into the text, or, when it's the same on every page, make it a [content variable](/docs/content/variables), which every surface replaces. Anything that can't be converted faithfully — a custom component, or a prop computed by a function call or from an import — is left as-is, and component markup inside code, fenced or inline, is never touched. The same conversion applies to [`llms-full.txt`](/docs/discoverability/llms-txt) and the [MCP server](/docs/discoverability/mcp)'s `get_page` tool, so every agent-facing surface reads clean Markdown.
21
21
 
22
- When you want the MDX itself, use the `.mdx` variant: its components stay as written, and the rest is prepared for an agent reading it by URL, as in the `.md` variant. [Includes](/docs/content/includes) are spliced in, [`<Visibility>`](/docs/content/components#visibility) resolves for agents (`for="web"` content is removed, `for="agents"` content kept), relative images point at their served URLs, relative page links point at the routes they mean, and root-relative links (`/guides/install`) gain the site's `deployment.base` and `basePath` and, on a translated page, move into the page's locale, the way the rendered page's links do. Root-relative images and links to files in `public/` (`/spec.pdf`) gain `deployment.base` alone, since that's where those files are served.
22
+ When you want the MDX itself, use the `.mdx` variant: its components stay as written, and the rest is prepared for an agent reading it by URL, as in the `.md` variant. [Includes](/docs/content/includes) are spliced in, [`<Visibility>`](/docs/content/components#visibility) resolves for agents (`for="web"` content is removed, `for="agents"` content kept), relative images and links to other files beside the page point at their served URLs, relative page links and root-relative links to a content file (`/guides/setup.md`) point at the routes they mean, and root-relative links (`/guides/install`) gain the site's `deployment.base` and `basePath` and, on a translated page, move into the page's locale, the way the rendered page's links do. Root-relative images and links to files in `public/` (`/spec.pdf`) gain `deployment.base` alone, since that's where those files are served.
23
23
 
24
24
  ### Content negotiation
25
25
 
@@ -49,7 +49,7 @@ seo:
49
49
  ```
50
50
 
51
51
  :::note
52
- Every palette color accepts any CSS color — hex, `oklch(…)`, `rgb(…)`, and so on. The accent also accepts a named preset (`blue`, `teal`, …), matching [`theme.accent`](/docs/configuration/theming#accent). A color the renderer can't parse fails the build rather than silently shipping a default-colored card.
52
+ Every palette color accepts any CSS color — hex, `oklch(…)`, `rgb(…)`, and so on. The accent also accepts a named preset (`blue`, `teal`, …), matching [`theme.accent`](/docs/configuration/theming#accent). A color the renderer can't parse fails the build rather than silently shipping a default-colored card, and one that isn't a CSS color at all also warns `BLUME_THEME_COLOR_INVALID` with the line that sets it, before the card renders.
53
53
  :::
54
54
 
55
55
  Emoji in a page title or site title render as [Twemoji](https://github.com/jdecked/twemoji) glyphs, fetched from a CDN while the card renders — so a build whose titles contain emoji needs network access. Each glyph is fetched once per build, however many pages use it.
@@ -7,7 +7,7 @@ Search crawlers and AI crawlers both start from the same two files at your site
7
7
 
8
8
  ## Sitemap
9
9
 
10
- Blume writes a `sitemap.xml` of every indexable page at build time. It needs an absolute [`deployment.site`](/docs/deployment) and lists every page except drafts, hidden, and [`noindex`](/docs/discoverability/metadata#per-page-overrides) pages, and pages whose `seo.canonical` names another URL. On a [versioned](/docs/content/versioning) site, archived pages whose canonical points at their live equivalent are left out too — the live page is the one to index. On by default:
10
+ Blume writes a `sitemap.xml` of every indexable page at build time. It needs an absolute [`deployment.site`](/docs/deployment) and lists every page except drafts, [hidden](/docs/content/navigation#hidden-pages), and [`noindex`](/docs/discoverability/metadata#per-page-overrides) pages, and pages whose `seo.canonical` names another URL. On a [versioned](/docs/content/versioning) site, archived pages whose canonical points at their live equivalent are left out too — the live page is the one to index. On by default:
11
11
 
12
12
  ```ts blume.config.ts lineNumbers
13
13
  seo: {
@@ -53,6 +53,8 @@ reference: [
53
53
  ],
54
54
  ```
55
55
 
56
+ An id Blume doesn't generate a sample for is left out, with a `BLUME_GRAPHQL_UNKNOWN_CODE_SAMPLE` warning that lists the ones it does.
57
+
56
58
  ## Type pages
57
59
 
58
60
  Named types get their own deep-linkable pages, grouped by kind in the sidebar: fields and input fields with their types linked, enum values, union members, interface implementations, and a **Used by** section listing the operations that return or accept the type and the other types that reference it. Spec-defined scalars (`String`, `Int`, …) don't get pages; custom scalars do, including their `specifiedBy` URL.
@@ -20,7 +20,7 @@ export default defineConfig({
20
20
 
21
21
  That mounts the reference at `/reference` (an overview page) with each operation at `/reference/<tag>/<operation>`. The `spec` is either an `http(s)` URL or a path to a local file in your project. Blume parses it with [Scalar's OpenAPI parser](https://github.com/scalar/scalar) — Swagger 2.0 and OpenAPI 3.0 specs are upgraded to 3.1 automatically, and 3.1 and [3.2](#openapi-32) specs are read as written. An adapter is a plain description of the reference — not a parsed spec — so Blume can validate it up front and inline it into the generated site; leaving `reference` out (or empty) renders no reference at all. Documenting an event-driven or GraphQL API instead? See [AsyncAPI](/docs/references/asyncapi) and [GraphQL](/docs/references/graphql).
22
22
 
23
- To go from your framework's generated spec to a deployed reference, follow the guide for [FastAPI](/guides/fastapi-documentation-website), [Django REST Framework](/guides/django-rest-framework-api-documentation), [NestJS](/guides/nestjs-api-documentation-website), [Spring Boot](/guides/spring-boot-api-documentation), [Hono](/guides/hono-zod-api-documentation), [Fastify](/guides/fastify-api-documentation), [Elysia](/guides/elysia-api-documentation), or [oRPC](/guides/orpc-openapi-documentation). [Generate API docs from an OpenAPI spec](/guides/openapi-documentation-website) starts from any spec file.
23
+ To go from your framework's generated spec to a deployed reference, follow the guide for [FastAPI](/guides/fastapi-documentation-website), [Django REST Framework](/guides/django-rest-framework-api-documentation), [NestJS](/guides/nestjs-api-documentation-website), [Spring Boot](/guides/spring-boot-api-documentation), [ASP.NET Core](/guides/aspnet-core-api-documentation), [Laravel](/guides/laravel-api-documentation-scramble), [Rails](/guides/rails-api-documentation-rswag), [Hono](/guides/hono-zod-api-documentation), [Fastify](/guides/fastify-api-documentation), [Elysia](/guides/elysia-api-documentation), or [oRPC](/guides/orpc-openapi-documentation). If you define the API in TypeSpec, [publish it from the TypeSpec definition](/guides/typespec-documentation-website). [Generate API docs from an OpenAPI spec](/guides/openapi-documentation-website) starts from any spec file.
24
24
 
25
25
  The reference doesn't add a header tab on its own. To surface it, point a [navigation tab](/docs/content/navigation#tabs) at its route — this also scopes the operations sidebar for the native renderer:
26
26
 
@@ -30,10 +30,14 @@ navigation: {
30
30
  }
31
31
  ```
32
32
 
33
- The sidebar's tag groups and the overview page's sections follow the same order: the order of the spec's top-level `tags` list, then any tag it doesn't declare, in the order operations first use it.
33
+ The sidebar's tag groups and the overview page's sections follow the same order: the order of the spec's top-level `tags` list, then any tag it doesn't declare, in the order operations first use it. Within a tag, operations follow the spec too: paths in the order they're listed, and each path's methods in the order they're written. A [`meta.ts`](/docs/content/meta) in a tag's folder with its own `pages` list overrides that order.
34
+
35
+ A tag's `x-displayName`, Redocly's extension, labels its sidebar group and overview section in place of its name. Its URL still comes from its name, so adding one doesn't move any page. An operation is labeled by its `summary`, or by its method and path (`GET /pets`), as its page is titled, when it has none.
36
+
37
+ The spec's `info.description` opens the overview page, and each tag's `description` opens its section there. Their headings move one level under where they sit, so the page's title stays its only top-level heading: a `# Authentication` in `info.description` or an operation's `description` renders as a second-level heading, and in a tag's description as a third-level one, under the tag's own.
34
38
 
35
39
  :::note
36
- Operations are indexed for search by their **summary, description, tag, and endpoint** (`GET /pets/{id}`). The rendered schema tables and generated code samples aren't full-text indexed; search matches an operation's title, prose, section, and path, then links to its own page. [Your own samples](#your-own-samples) are indexed like any code block, only with [`search.indexing.includeCodeBlocks`](/docs/configuration/search).
40
+ Operations are indexed for search by their **summary, description, tag, and endpoint** (`GET /pets/{id}`), and by what their [Markdown copy](#your-own-samples) lists: the request body's top-level properties and the responses' descriptions. The rendered schema tables and generated code samples aren't full-text indexed; search matches an operation's title, prose, section, and path, then links to its own page. [Your own samples](#your-own-samples) are indexed like any code block, only with [`search.indexing.includeCodeBlocks`](/docs/configuration/search).
37
41
  :::
38
42
 
39
43
  ## A local spec
@@ -67,6 +71,10 @@ reference: [
67
71
  ],
68
72
  ```
69
73
 
74
+ An operation's `<tag>` is its first tag (`operations` when it has none), and its `<operation>` is its `operationId`, else its method and path. Both are lowercased with their words split by hyphens, camelCase and PascalCase included: the tag `InboxesThreads` gives `inboxes-threads`, and the operationId `listThreads` gives `list-threads`. An operation's slug only has to be unique within its tag, so two tags can each have a `list`; a second `list` in the same tag gets its method added (`list-post`), with a [`BLUME_OPENAPI_DUPLICATE_OPERATION_ID`](#spec-warnings) warning when the two share an `operationId`.
75
+
76
+ Earlier versions of Blume kept a tag's words together (`inboxesthreads`) and made an operation's slug unique across the whole spec (`list-get` for the second `list`). Blume redirects each of those URLs, and its Markdown copy, to the page's current one, unless a page or one of your [`redirects`](/docs/deployment#redirects) is already at that URL.
77
+
70
78
  ## Code samples and schemas
71
79
 
72
80
  `codeSamples` picks which languages render per operation, in the order you list them. It defaults to `["curl", "js", "python"]`, and `false` or an empty list shows none. `expandSchemas` starts nested schema rows expanded rather than collapsed:
@@ -99,14 +107,20 @@ Every sample is plain, copy-pasteable code for the request, written with the lan
99
107
  | `csharp` | C# | `HttpClient` | `c#`, `cs` |
100
108
  | `dotnet` | .NET | RestSharp | `.net`, `dot-net` |
101
109
  | `c` | C | libcurl | |
102
- | `cpp` | C++ | cpr | `c++` |
110
+ | `cpp` | C++ | cpr | `c++`, `cplusplus` |
103
111
  | `kotlin` | Kotlin | OkHttp | `kt` |
104
112
  | `rust` | Rust | reqwest | `rs` |
105
113
  | `dart` | Dart | `package:http` | `flutter` |
106
114
 
107
115
  Each value is quoted by its own language's rules, so a header like `If-Match: "33a64df5"` or a body with a `$` in it reaches the API as written.
108
116
 
109
- Schema tables follow the way the data travels, as the examples do: a request body leaves out `readOnly` properties, which the server sets, and a response leaves out `writeOnly` ones, which it never returns. A [webhook or callback](#webhooks-and-callbacks) body is a request your API sends, so it reads like a response.
117
+ An id Blume doesn't generate a sample for, such as ReadMe's `objectivec`, is left out of every page, with a `BLUME_OPENAPI_UNKNOWN_CODE_SAMPLE` warning that lists the ids above. To show a sample in another language, write it into the spec as [your own sample](#your-own-samples).
118
+
119
+ Schema tables follow the way the data travels, as the examples do: a request body leaves out `readOnly` properties, which the server sets, and a response leaves out `writeOnly` ones, which it never returns. A [webhook or callback](#webhooks-and-callbacks) body is a request your API sends, so it reads like a response. The same goes for the request samples, the [Try it](#try-it-playground) form and its prefill, and the response examples, including examples written into the spec, such as a model-level `example` that holds the model's `id`.
120
+
121
+ A body or response whose `examples` map names two or more examples shows each one, labeled by its `summary`, or by its key when it has none. A response gets a tab per example after its status (`200 · A cat`), a webhook payload one per example after its media type, and a request body an **Examples** block under its schema. The code samples and Try it start from the first.
122
+
123
+ A property written as a `$ref` with keywords beside it, which OpenAPI 3.1 allows, reads those keywords over the referenced schema's. Its own `description` is the one its row shows, and its own `example` or `default` is the one the samples use.
110
124
 
111
125
  ### Your own samples
112
126
 
@@ -127,7 +141,7 @@ paths:
127
141
  planter list
128
142
  ```
129
143
 
130
- These samples render as their own tabs, ahead of the generated ones, and stay as written when readers change the [Try it](#try-it-playground) form. The older `x-code-samples` spelling works too. To show only your own samples, set `codeSamples: false`. Unlike the generated samples, they're also in the operation page's Markdown copy, so `llms-full.txt`, the MCP server, and the assistant see your SDK calls.
144
+ These samples render as their own tabs, ahead of the generated ones, and stay as written when readers change the [Try it](#try-it-playground) form. The older `x-code-samples` spelling works too. To show only your own samples, set `codeSamples: false`. Unlike the generated samples, they're also in the operation page's Markdown copy, so `llms-full.txt`, the MCP server, and the assistant see your SDK calls. That copy also lists the request body's media type and top-level properties with its example (every named one), and each response's status and description, with the examples the spec records and a sampled one for the first success response that records none.
131
145
 
132
146
  A `source` can also be a `$ref` to a file that holds the sample's code, the way Redocly describes keeping samples in files of their own. Blume reads the file relative to the spec, fetching it when the spec is a URL. A `$ref` it can't read, or one that points inside a document (`#/…`), leaves that sample out with a [`BLUME_OPENAPI_CODE_SAMPLE_REF`](#spec-warnings) warning:
133
147
 
@@ -147,6 +161,8 @@ paths:
147
161
 
148
162
  Operation pages rendered natively ship an interactive **Try it** panel by default. Blume generates the form from the operation itself: an input per path, query, and header parameter, a body editor built from the request-body schema, everything prefilled from the spec's examples. Values go on the wire the way the spec describes them: array and object parameters follow their `style` and `explode` (`tags=dog&tags=cat` in a query, `1,2` in a path, `filter[color]=red` for `deepObject`), and an `application/x-www-form-urlencoded` or `multipart/form-data` body is sent as form fields rather than JSON. A server picker lists the operation's servers (its own `servers`, else its path's, else the spec's, with each `{variable}` at its `default`), with a free-text field for any other base URL, and auth inputs match the operation's [resolved security](#authorization) — bearer token, API key, and basic credentials, with OAuth2 as a token paste field (bring an access token; Blume doesn't run the flow).
149
163
 
164
+ When any of the operation's responses is JSON, requests ask for it: Send and the code samples carry an `Accept` header with the first JSON media type the responses list, a success response's ahead of the others. Frameworks like Laravel answer a request without one as if a browser sent it, redirecting an unauthenticated call to a login page instead of returning the 401 the API documents. An `Accept` header parameter the operation declares takes its place.
165
+
150
166
  The panel and the code samples stay in lockstep: values typed into the form update the generated samples live, so a copied curl command always matches exactly what **Send** would do. And it stays out of the way — the panel is server-rendered collapsed, and its JavaScript loads only when a reader first opens it. Readers who never touch it download none of it.
151
167
 
152
168
  The one method a browser can't send is `TRACE`: `fetch` refuses it, so on a `trace` operation **Send** says so instead of sending, and the JavaScript sample (built on `fetch`) is a note rather than code. The cURL and Python samples send it fine.
@@ -176,7 +192,7 @@ reference: [
176
192
  ],
177
193
  ```
178
194
 
179
- The built-in proxy only forwards requests to the origins your specs declare in `servers` (at the document, path, or operation level, with variables at their defaults) — including across redirects — so a public docs deployment can't be aimed at other hosts on its network. A **Custom base URL** typed into the panel isn't a documented server: with the proxy enabled, requests to it are refused with a 403. It forwards only the headers the panel sets itself — the credentials and header parameters filled in, and the body's `Content-Type` — so cookies, credentials the browser attaches for the docs site (HTTP Basic auth on a password-protected preview, say), and headers your host adds (`X-Forwarded-For`, `CF-*`, `X-Vercel-*`) never reach the API. It reads a request body only up to 4 MB (anything larger gets a `413`), and every response it relays carries `Content-Security-Policy: sandbox`, `X-Content-Type-Options: nosniff`, and `Cross-Origin-Resource-Policy: same-origin` — plus `Content-Disposition: attachment` for HTML or SVG — so an API error page that echoes its input can't run script on the docs origin. The proxy is also [rate limited](/docs/configuration/rate-limiting) per reader, like every server route a reader can call.
195
+ The built-in proxy only forwards requests to the origins your specs declare in `servers` (at the document, path, or operation level, with variables at their defaults) — including across redirects — so a public docs deployment can't be aimed at other hosts on its network. A **Custom base URL** typed into the panel isn't a documented server: with the proxy enabled, requests to it are refused with a 403. It forwards only the headers the panel sets itself — the credentials and header parameters filled in, the `Accept` header, and the body's `Content-Type` — so cookies, credentials the browser attaches for the docs site (HTTP Basic auth on a password-protected preview, say), and headers your host adds (`X-Forwarded-For`, `CF-*`, `X-Vercel-*`) never reach the API. It reads a request body only up to 4 MB (anything larger gets a `413`), and every response it relays carries `Content-Security-Policy: sandbox`, `X-Content-Type-Options: nosniff`, and `Cross-Origin-Resource-Policy: same-origin` — plus `Content-Disposition: attachment` for HTML or SVG — so an API error page that echoes its input can't run script on the docs origin. The proxy is also [rate limited](/docs/configuration/rate-limiting) per reader, like every server route a reader can call.
180
196
 
181
197
  When a Try it request is blocked, [Fix CORS errors in an API documentation playground](/guides/api-documentation-cors-errors) reproduces it, reads the preflight the browser sends, and fixes it with a CORS policy on your API or the built-in proxy.
182
198
 
@@ -195,6 +211,8 @@ reference: [
195
211
  ],
196
212
  ```
197
213
 
214
+ A source's group sits at its route, and each folder of the route above it is a sidebar group too. So route a spec at the folder it describes: `route: "/apis/messaging"` makes the source's group the `messaging` folder's own group, named by its `label`, while `"/apis/messaging/openapi"` nests that group inside a **Messaging** group that holds nothing else.
215
+
198
216
  `spec` is shorthand for a single-entry `sources`, so you only reach for `sources` when you have more than one. When two specs need different display options — a different code-sample set, say — list two `openapi()` adapters instead, each with its own `route`. An embedded [Scalar](/docs/references/scalar) reference beside native pages is its own `scalar()` adapter in the list. Sources are resolved in list order, and when two resolve to the same route the first one wins (the build warns about the one it dropped).
199
217
 
200
218
  [Combine multiple OpenAPI specifications in one docs site](/guides/multiple-openapi-specs-documentation) builds an API portal this way, with each service's spec on its own route and tab.
@@ -320,9 +338,29 @@ Blume doesn't validate a spec against the OpenAPI schema: run a linter such as [
320
338
  | `BLUME_OPENAPI_CODE_SAMPLE_REF` | An `x-codeSamples` entry's `source` is a `$ref` Blume can't read, or one that points inside a document, so that [sample](#your-own-samples) is left out. |
321
339
  | `BLUME_OPENAPI_X_WEBHOOKS` | A 3.1 or later spec keeps its webhooks under `x-webhooks`, the extension 3.0 tools read, and has no `webhooks`, so none of them render. Rename the field to `webhooks`. |
322
340
  | `BLUME_OPENAPI_REF_PATH_ITEM` | A path or webhook is a `$ref` to a shared path item, such as one under `components.pathItems`. Blume doesn't resolve these, so its operations are missing from the reference. Inline the path item under `paths` or `webhooks`; its schemas, parameters, and responses can stay `$ref`s. |
341
+ | `BLUME_OPENAPI_DUPLICATE_OPERATION_ID` | Two operations share an `operationId`, which must be unique in a spec. The warning names both. Blume adds the method to the second one's id in `<Operation id>`, and to its [URL](#route) when the two share a tag. Give each operation its own `operationId`. |
342
+ | `BLUME_OPENAPI_LOCAL_SERVER` | A server the spec, a path, or an operation declares is a local address (`localhost`, `127.0.0.1`, `0.0.0.0`, `[::1]`), often written by a framework that exported the spec on a dev machine. Try it and the code samples would send requests there, which readers can't reach. List the public URL in `servers`, or set it with an [overlay](#overlays). |
343
+ | `BLUME_OPENAPI_NULLABLE` | A spec that declares OpenAPI 3.1 or later uses `nullable`, which 3.1 removed. Blume still shows those values as nullable, but validators and SDK generators that follow 3.1 read them as never null. The warning names where, as JSON Pointers into the spec. Add `"null"` to the schema's `type` instead (`type: [string, "null"]`). |
323
344
 
324
345
  [Keep API documentation in sync with backend changes in CI](/guides/openapi-documentation-ci) runs those checks in GitHub Actions: it exports the spec from the backend, fails when the spec is stale, invalid, or breaking, and rebuilds the reference from it.
325
346
 
347
+ ## Operations in your own pages
348
+
349
+ Any MDX page can show an operation from a reference with `<Operation>`: its authorization, parameters, request body, and responses, beside the Try it panel and code samples, as the operation's own page shows them. A quickstart can walk through the one call it's about without sending readers away:
350
+
351
+ ```mdx docs/quickstart.mdx lineNumbers
352
+ ---
353
+ title: Quickstart
354
+ mode: wide
355
+ ---
356
+
357
+ Create your first pet:
358
+
359
+ <Operation source="reference" id="add-pet" />
360
+ ```
361
+
362
+ `source` names the reference by its route, without slashes and lowercased with hyphens (`/reference` is `reference`, `/api/v2` is `api-v2`). `id` names the operation: its `operationId`, lowercased with its words split by hyphens (`addPet` is `add-pet`), or its method and path (`post-pets`) when it has none. The surest way to get both is an operation page's raw MDX copy, at its URL plus `.mdx`, whose body is the tag to copy. The operation lays out in two columns, so give the page [`mode: wide`](/docs/content/frontmatter#layout): beside the table of contents, the left column is narrow. In the page's Markdown copy the tag becomes the endpoint, request body, and responses, as on the operation's own page.
363
+
326
364
  ## Embedding Scalar instead
327
365
 
328
366
  `openapi()` always renders Blume's own pages. To embed [Scalar](https://scalar.com)'s self-contained API reference UI on a single route instead — its own sidebar, search, theme, and request client — list a `scalar()` adapter from `blume/reference` in place of (or beside) this one. The [Scalar](/docs/references/scalar) page covers what the embed does and doesn't do, and how to pass Scalar's own options through.