blume 2.1.3 → 2.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (397) hide show
  1. package/AGENTS.md +1 -1
  2. package/CHANGELOG.md +134 -0
  3. package/README.md +4 -4
  4. package/dist/cli/{chunk-y4msrwm9.js → chunk-080mfch6.js} +15 -15
  5. package/dist/cli/{chunk-gfc5fqvf.js → chunk-1ch3ct3r.js} +18 -18
  6. package/dist/cli/{chunk-gfc5fqvf.js.map → chunk-1ch3ct3r.js.map} +1 -1
  7. package/dist/cli/{chunk-32khwcb6.js → chunk-2ssh60qh.js} +42 -69
  8. package/dist/cli/{chunk-32khwcb6.js.map → chunk-2ssh60qh.js.map} +9 -10
  9. package/dist/cli/{chunk-9qzgg6mj.js → chunk-34wap3ed.js} +3 -3
  10. package/dist/cli/{chunk-k3gr1nm9.js → chunk-40vk8kgf.js} +13 -16
  11. package/dist/cli/{chunk-k3gr1nm9.js.map → chunk-40vk8kgf.js.map} +3 -3
  12. package/dist/cli/chunk-487b95qy.js +329 -0
  13. package/dist/cli/chunk-487b95qy.js.map +13 -0
  14. package/dist/cli/chunk-4zxh25hc.js +157 -0
  15. package/dist/cli/chunk-4zxh25hc.js.map +10 -0
  16. package/dist/cli/chunk-69rjf786.js +146 -0
  17. package/dist/cli/chunk-69rjf786.js.map +11 -0
  18. package/dist/cli/{chunk-dqhg54mr.js → chunk-9h8mh2tk.js} +3 -3
  19. package/dist/cli/{chunk-dqhg54mr.js.map → chunk-9h8mh2tk.js.map} +2 -2
  20. package/dist/cli/{chunk-bqxaqn5p.js → chunk-9jqmq5hv.js} +8 -8
  21. package/dist/cli/{chunk-bqxaqn5p.js.map → chunk-9jqmq5hv.js.map} +1 -1
  22. package/dist/cli/{chunk-dv2z0rzf.js → chunk-aafhwx74.js} +38 -13
  23. package/dist/cli/{chunk-dv2z0rzf.js.map → chunk-aafhwx74.js.map} +5 -4
  24. package/dist/cli/{chunk-desvm72z.js → chunk-b680sv30.js} +22 -26
  25. package/dist/cli/chunk-b680sv30.js.map +13 -0
  26. package/dist/cli/{chunk-0w6hs479.js → chunk-beb0z983.js} +30 -18
  27. package/dist/cli/chunk-beb0z983.js.map +10 -0
  28. package/dist/cli/{chunk-1f882900.js → chunk-c5bjxq8v.js} +2 -2
  29. package/dist/cli/chunk-e1n41w9m.js +155 -0
  30. package/dist/cli/chunk-e1n41w9m.js.map +11 -0
  31. package/dist/cli/{chunk-sc93apfs.js → chunk-etckvbrh.js} +17 -17
  32. package/dist/cli/{chunk-sc93apfs.js.map → chunk-etckvbrh.js.map} +2 -2
  33. package/dist/cli/{chunk-bxwhtvw8.js → chunk-f260jdrn.js} +5 -5
  34. package/dist/cli/{chunk-74wfhv79.js → chunk-fnxgtjeq.js} +1 -1
  35. package/dist/cli/{chunk-ms9z66nz.js → chunk-gwy55cx3.js} +2 -2
  36. package/dist/cli/{chunk-ms9z66nz.js.map → chunk-gwy55cx3.js.map} +2 -2
  37. package/dist/cli/{chunk-dhwtqnnd.js → chunk-gxhcf2hs.js} +2 -2
  38. package/dist/cli/{chunk-tfycddbt.js → chunk-h0qwdbem.js} +2 -2
  39. package/dist/cli/{chunk-nbyvgnnm.js → chunk-hktq4j5r.js} +109 -49
  40. package/dist/cli/chunk-hktq4j5r.js.map +36 -0
  41. package/dist/cli/{chunk-twy7psdz.js → chunk-hn3g2b89.js} +14 -10
  42. package/dist/cli/chunk-hn3g2b89.js.map +10 -0
  43. package/dist/cli/{chunk-j1x92mx9.js → chunk-j4nfgn2j.js} +1 -1
  44. package/dist/cli/{chunk-dpy8qdjf.js → chunk-k45sq16q.js} +74 -183
  45. package/dist/cli/chunk-k45sq16q.js.map +18 -0
  46. package/dist/cli/{chunk-5cmts6dv.js → chunk-kmswjxj9.js} +7 -7
  47. package/dist/cli/{chunk-5cmts6dv.js.map → chunk-kmswjxj9.js.map} +1 -1
  48. package/dist/cli/{chunk-36bxs9c7.js → chunk-nqna8dzp.js} +27 -23
  49. package/dist/cli/chunk-nqna8dzp.js.map +10 -0
  50. package/dist/cli/{chunk-a57yh0h9.js → chunk-p834rkcn.js} +7 -7
  51. package/dist/cli/{chunk-a57yh0h9.js.map → chunk-p834rkcn.js.map} +1 -1
  52. package/dist/cli/{chunk-60hz265r.js → chunk-ppa6mg4d.js} +24 -11
  53. package/dist/cli/chunk-ppa6mg4d.js.map +10 -0
  54. package/dist/cli/{chunk-s0bdnc7v.js → chunk-q1wwrxzw.js} +5 -4
  55. package/dist/cli/chunk-q1wwrxzw.js.map +10 -0
  56. package/dist/cli/{chunk-ave1wcbr.js → chunk-qcyxkj44.js} +1 -1
  57. package/dist/cli/{chunk-124fvmzq.js → chunk-r5nm9w0c.js} +11183 -8806
  58. package/dist/cli/{chunk-124fvmzq.js.map → chunk-r5nm9w0c.js.map} +82 -66
  59. package/dist/cli/{chunk-ckrcm08h.js → chunk-rtvgmg2j.js} +48 -62
  60. package/dist/cli/{chunk-ckrcm08h.js.map → chunk-rtvgmg2j.js.map} +4 -5
  61. package/dist/cli/{chunk-297ajdcv.js → chunk-t2h8pwy1.js} +6 -6
  62. package/dist/cli/{chunk-297ajdcv.js.map → chunk-t2h8pwy1.js.map} +3 -3
  63. package/dist/cli/{chunk-gxtg6ed2.js → chunk-x60e15st.js} +21 -21
  64. package/dist/cli/{chunk-gxtg6ed2.js.map → chunk-x60e15st.js.map} +3 -3
  65. package/dist/cli/{chunk-58bt3hag.js → chunk-x7bh6d9j.js} +4 -4
  66. package/dist/cli/{chunk-hvcxng5f.js → chunk-ydqzkhjn.js} +6 -6
  67. package/dist/cli/{chunk-kdpzw38a.js → chunk-yg8ckc3g.js} +95 -7
  68. package/dist/cli/chunk-yg8ckc3g.js.map +11 -0
  69. package/dist/cli/{chunk-0pmb4tr4.js → chunk-zxd5jrgx.js} +1205 -222
  70. package/dist/cli/chunk-zxd5jrgx.js.map +44 -0
  71. package/dist/cli/index.js +78 -42
  72. package/dist/cli/index.js.map +7 -6
  73. package/dist/types/ai/changelog-components.d.ts +18 -0
  74. package/dist/types/ai/changelog-markdown.d.ts +12 -0
  75. package/dist/types/ai/openapi-components.d.ts +7 -7
  76. package/dist/types/ai/operation-details.d.ts +39 -0
  77. package/dist/types/ai/relative-links.d.ts +3 -1
  78. package/dist/types/ai/serializers.d.ts +6 -4
  79. package/dist/types/analytics/head.d.ts +4 -2
  80. package/dist/types/analytics/script.d.ts +8 -2
  81. package/dist/types/components/content/youtube.d.ts +20 -0
  82. package/dist/types/components/layout/nav-utils.d.ts +8 -0
  83. package/dist/types/components/openapi/example-panels.d.ts +33 -0
  84. package/dist/types/components/openapi/helpers.d.ts +53 -11
  85. package/dist/types/components/openapi/snippets.d.ts +7 -1
  86. package/dist/types/core/alert-diagnostics.d.ts +10 -0
  87. package/dist/types/core/builtin-tags.d.ts +16 -0
  88. package/dist/types/core/changelog-list.d.ts +48 -0
  89. package/dist/types/core/closest-match.d.ts +6 -0
  90. package/dist/types/core/code-fence-diagnostics.d.ts +18 -0
  91. package/dist/types/core/code-fences.d.ts +8 -1
  92. package/dist/types/core/config-input.d.ts +38 -6
  93. package/dist/types/core/content-assets.d.ts +16 -18
  94. package/dist/types/core/data.d.ts +6 -2
  95. package/dist/types/core/diagnostics.d.ts +14 -0
  96. package/dist/types/core/directive-diagnostics.d.ts +22 -8
  97. package/dist/types/core/former-routes.d.ts +14 -0
  98. package/dist/types/core/github.d.ts +5 -4
  99. package/dist/types/core/heading-markers.d.ts +65 -2
  100. package/dist/types/core/hidden-pages.d.ts +3 -0
  101. package/dist/types/core/includes.d.ts +23 -0
  102. package/dist/types/core/last-modified.d.ts +18 -6
  103. package/dist/types/core/links.d.ts +42 -2
  104. package/dist/types/core/load-module.d.ts +19 -4
  105. package/dist/types/core/manifest.d.ts +14 -1
  106. package/dist/types/core/markdown-mirrors.d.ts +22 -0
  107. package/dist/types/core/mcp-clients.d.ts +47 -0
  108. package/dist/types/core/mdx-elements.d.ts +32 -0
  109. package/dist/types/core/mdx-syntax.d.ts +100 -0
  110. package/dist/types/core/meta-diagnostics.d.ts +7 -0
  111. package/dist/types/core/meta.d.ts +32 -5
  112. package/dist/types/core/nav-diagnostics.d.ts +16 -2
  113. package/dist/types/core/navigation.d.ts +13 -0
  114. package/dist/types/core/ordering-prefix.d.ts +6 -0
  115. package/dist/types/core/pinned-ids.d.ts +23 -0
  116. package/dist/types/core/project-graph.d.ts +9 -1
  117. package/dist/types/core/redirect-patterns.d.ts +18 -0
  118. package/dist/types/core/relative-files.d.ts +31 -0
  119. package/dist/types/core/schema.d.ts +322 -11
  120. package/dist/types/core/sources/normalize.d.ts +101 -9
  121. package/dist/types/core/sources/types.d.ts +16 -0
  122. package/dist/types/core/static-files.d.ts +10 -0
  123. package/dist/types/core/static-host.d.ts +42 -0
  124. package/dist/types/core/svg-images.d.ts +26 -0
  125. package/dist/types/core/syntax-diagnostics.d.ts +14 -0
  126. package/dist/types/core/types.d.ts +42 -1
  127. package/dist/types/deploy/generated-files.d.ts +41 -0
  128. package/dist/types/deploy/redirects.d.ts +21 -7
  129. package/dist/types/markdown/code-title.d.ts +9 -5
  130. package/dist/types/markdown/directives.d.ts +8 -0
  131. package/dist/types/markdown/fence-language.d.ts +51 -0
  132. package/dist/types/markdown/fence-meta.d.ts +5 -0
  133. package/dist/types/markdown/github-alerts.d.ts +35 -0
  134. package/dist/types/openapi/asyncapi.d.ts +6 -5
  135. package/dist/types/openapi/checks.d.ts +10 -5
  136. package/dist/types/openapi/model.d.ts +55 -8
  137. package/dist/types/openapi/parse.d.ts +4 -1
  138. package/dist/types/ratelimit/index.d.ts +2 -0
  139. package/dist/types/ratelimit/memory.d.ts +1 -1
  140. package/dist/types/ratelimit/schema.d.ts +40 -0
  141. package/dist/types/ratelimit/unkey.d.ts +48 -0
  142. package/dist/types/sources/filesystem.d.ts +14 -4
  143. package/dist/types/sources/mdx-remote.d.ts +6 -3
  144. package/dist/types/sources/registry.d.ts +3 -3
  145. package/dist/types/theme/color.d.ts +33 -0
  146. package/dist/types/theme/palette.d.ts +122 -0
  147. package/docs/01-quickstart.mdx +1 -1
  148. package/docs/02-deployment.mdx +24 -8
  149. package/docs/04-migrating.mdx +16 -4
  150. package/docs/advanced/changelog.mdx +27 -1
  151. package/docs/advanced/skills.mdx +1 -1
  152. package/docs/cli/audit.mdx +1 -1
  153. package/docs/cli/doctor.mdx +2 -2
  154. package/docs/cli/evals.mdx +1 -1
  155. package/docs/cli/index.mdx +4 -2
  156. package/docs/cli/validate.mdx +10 -7
  157. package/docs/configuration/analytics.mdx +2 -0
  158. package/docs/configuration/customization.mdx +3 -1
  159. package/docs/configuration/index.mdx +7 -6
  160. package/docs/configuration/rate-limiting.mdx +20 -1
  161. package/docs/configuration/theming.mdx +5 -1
  162. package/docs/content/components.mdx +26 -6
  163. package/docs/content/frontmatter.mdx +1 -1
  164. package/docs/content/i18n.mdx +1 -1
  165. package/docs/content/includes.mdx +6 -2
  166. package/docs/content/index.mdx +11 -1
  167. package/docs/content/islands.mdx +2 -0
  168. package/docs/content/meta.mdx +9 -3
  169. package/docs/content/navigation.mdx +14 -6
  170. package/docs/content/sources/obsidian.mdx +1 -1
  171. package/docs/content/sources/remote-mdx.mdx +3 -1
  172. package/docs/content/syntax.mdx +76 -8
  173. package/docs/content/variables.mdx +3 -1
  174. package/docs/discoverability/agent-discovery.mdx +1 -1
  175. package/docs/discoverability/json-api.mdx +1 -1
  176. package/docs/discoverability/llms-txt.mdx +1 -1
  177. package/docs/discoverability/markdown.mdx +2 -2
  178. package/docs/discoverability/mcp.mdx +40 -8
  179. package/docs/discoverability/open-graph.mdx +1 -1
  180. package/docs/discoverability/sitemap-and-robots.mdx +1 -1
  181. package/docs/references/graphql.mdx +2 -0
  182. package/docs/references/openapi.mdx +45 -7
  183. package/package.json +4 -4
  184. package/skills/blume/SKILL.md +2 -2
  185. package/skills/blume-migrate/SKILL.md +51 -29
  186. package/skills/blume-migrate/references/docsify.md +262 -0
  187. package/skills/blume-migrate/references/docus.md +298 -0
  188. package/skills/blume-migrate/references/docusaurus.md +5 -5
  189. package/skills/blume-migrate/references/fern.md +327 -0
  190. package/skills/blume-migrate/references/fumadocs.md +1 -1
  191. package/skills/blume-migrate/references/gitbook.md +267 -0
  192. package/skills/blume-migrate/references/github-wiki.md +259 -0
  193. package/skills/blume-migrate/references/jekyll.md +253 -0
  194. package/skills/blume-migrate/references/mdbook.md +256 -0
  195. package/skills/blume-migrate/references/mintlify.md +5 -5
  196. package/skills/blume-migrate/references/mkdocs.md +236 -0
  197. package/skills/blume-migrate/references/monorepo.md +20 -8
  198. package/skills/blume-migrate/references/nextra.md +5 -5
  199. package/skills/blume-migrate/references/readme.md +257 -0
  200. package/skills/blume-migrate/references/redocly.md +198 -0
  201. package/skills/blume-migrate/references/starlight.md +3 -3
  202. package/skills/blume-migrate/references/vitepress.md +204 -0
  203. package/skills/blume-migrate/references/vuepress.md +271 -0
  204. package/skills/blume-migrate/scripts/docsify-codemod.mjs +2387 -0
  205. package/skills/blume-migrate/scripts/docus-codemod.mjs +2542 -0
  206. package/skills/blume-migrate/scripts/fern-codemod.mjs +2633 -0
  207. package/skills/blume-migrate/scripts/github-wiki-codemod.mjs +2808 -0
  208. package/skills/blume-migrate/scripts/include-excerpts.mjs +649 -0
  209. package/skills/blume-migrate/scripts/jekyll-codemod.mjs +3649 -0
  210. package/skills/blume-migrate/scripts/mintlify-codemod.mjs +17 -13
  211. package/skills/blume-migrate/scripts/mkdocs-codemod.mjs +2526 -0
  212. package/skills/blume-migrate/scripts/operation-routes.mjs +508 -0
  213. package/skills/blume-migrate/scripts/pin-heading-ids.mjs +1049 -0
  214. package/skills/blume-migrate/scripts/readme-codemod.mjs +4139 -0
  215. package/skills/blume-migrate/scripts/vitepress-codemod.mjs +1511 -0
  216. package/src/ai/changelog-components.ts +24 -0
  217. package/src/ai/changelog-markdown.ts +45 -41
  218. package/src/ai/component-markdown.ts +22 -9
  219. package/src/ai/llms.ts +36 -5
  220. package/src/ai/markdown.ts +2 -2
  221. package/src/ai/openapi-components.ts +25 -9
  222. package/src/ai/operation-details.ts +208 -0
  223. package/src/ai/relative-links.ts +48 -8
  224. package/src/ai/serializers.ts +7 -6
  225. package/src/ai/site-skill.ts +6 -2
  226. package/src/analytics/head.ts +11 -5
  227. package/src/analytics/script.ts +13 -3
  228. package/src/astro/generate.ts +47 -11
  229. package/src/astro/index.ts +1 -0
  230. package/src/astro/integration.ts +27 -1
  231. package/src/astro/mdx-source-errors.ts +46 -0
  232. package/src/astro/templates.ts +93 -189
  233. package/src/audit/checks/redirects.ts +5 -1
  234. package/src/audit/redirects.ts +36 -19
  235. package/src/audit/report.ts +22 -9
  236. package/src/audit/run.ts +12 -28
  237. package/src/audit/terms.ts +23 -0
  238. package/src/audit/url.ts +2 -2
  239. package/src/cli/build-failure.ts +256 -7
  240. package/src/cli/command-meta.ts +1 -1
  241. package/src/cli/commands/audit.ts +1 -1
  242. package/src/cli/commands/build.ts +12 -3
  243. package/src/cli/commands/check.ts +13 -2
  244. package/src/cli/commands/dev.ts +1 -1
  245. package/src/cli/commands/doctor.ts +4 -2
  246. package/src/cli/commands/init.ts +3 -3
  247. package/src/cli/commands/preview.ts +17 -1
  248. package/src/cli/commands/validate.ts +22 -10
  249. package/src/cli/prepare.ts +1 -1
  250. package/src/cli/preview-pages.ts +161 -0
  251. package/src/cli/required-secrets.ts +0 -1
  252. package/src/cli/unknown-flags.ts +1 -42
  253. package/src/components/code-copy.ts +51 -0
  254. package/src/components/code-language.ts +75 -0
  255. package/src/components/content/AccordionItem.astro +16 -1
  256. package/src/components/content/Callout.astro +11 -1
  257. package/src/components/content/Changelog.astro +189 -0
  258. package/src/components/content/CodeBlock.astro +8 -11
  259. package/src/components/content/Tabs.astro +16 -2
  260. package/src/components/content/YouTube.astro +6 -4
  261. package/src/components/content/code-block.ts +33 -0
  262. package/src/components/content/content-strings.ts +5 -1
  263. package/src/components/content/inline-markdown.ts +35 -8
  264. package/src/components/content/youtube.ts +49 -7
  265. package/src/components/layout/Analytics.astro +5 -1
  266. package/src/components/layout/NavTree.astro +8 -3
  267. package/src/components/layout/PageActions.astro +81 -23
  268. package/src/components/layout/RootLayout.astro +35 -32
  269. package/src/components/layout/SiteFooter.astro +31 -8
  270. package/src/components/layout/nav-utils.ts +14 -0
  271. package/src/components/layout/search/pagefind.ts +2 -1
  272. package/src/components/openapi/Operation.astro +4 -0
  273. package/src/components/openapi/PanelTabs.astro +9 -3
  274. package/src/components/openapi/RequestBody.astro +42 -8
  275. package/src/components/openapi/RequestPanel.astro +34 -53
  276. package/src/components/openapi/example-panels.ts +110 -0
  277. package/src/components/openapi/helpers.ts +195 -20
  278. package/src/components/openapi/operation-model.ts +63 -26
  279. package/src/components/openapi/request.ts +13 -0
  280. package/src/components/openapi/snippets.ts +14 -2
  281. package/src/core/alert-diagnostics.ts +89 -0
  282. package/src/core/builtin-tags.ts +76 -0
  283. package/src/core/changelog-list.ts +87 -0
  284. package/src/core/closest-match.ts +41 -0
  285. package/src/core/code-fence-diagnostics.ts +267 -0
  286. package/src/core/code-fences.ts +43 -7
  287. package/src/core/component-diagnostics.ts +65 -12
  288. package/src/core/config-input.ts +39 -5
  289. package/src/core/config.ts +46 -1
  290. package/src/core/content-assets.ts +64 -144
  291. package/src/core/data.ts +10 -3
  292. package/src/core/diagnostics.ts +36 -3
  293. package/src/core/directive-diagnostics.ts +265 -47
  294. package/src/core/former-routes.ts +46 -0
  295. package/src/core/git-ignored.ts +53 -0
  296. package/src/core/github.ts +5 -4
  297. package/src/core/heading-markers.ts +156 -2
  298. package/src/core/hidden-pages.ts +64 -0
  299. package/src/core/includes.ts +181 -41
  300. package/src/core/last-modified.ts +47 -10
  301. package/src/core/links.ts +438 -83
  302. package/src/core/load-module.ts +98 -9
  303. package/src/core/manifest.ts +12 -9
  304. package/src/core/markdown-mirrors.ts +52 -0
  305. package/src/core/mcp-clients.ts +87 -0
  306. package/src/core/mdx-elements.ts +153 -0
  307. package/src/core/mdx-syntax.ts +377 -0
  308. package/src/core/meta-diagnostics.ts +134 -0
  309. package/src/core/meta.ts +154 -55
  310. package/src/core/nav-diagnostics.ts +41 -4
  311. package/src/core/navigation.ts +70 -10
  312. package/src/core/ordering-prefix.ts +22 -2
  313. package/src/core/pinned-ids.ts +141 -0
  314. package/src/core/project-graph.ts +221 -29
  315. package/src/core/redirect-patterns.ts +41 -0
  316. package/src/core/relative-files.ts +145 -0
  317. package/src/core/schema.ts +110 -6
  318. package/src/core/sources/mdx-remote.ts +75 -14
  319. package/src/core/sources/normalize.ts +885 -179
  320. package/src/core/sources/notion.ts +5 -4
  321. package/src/core/sources/obsidian.ts +2 -5
  322. package/src/core/sources/types.ts +17 -0
  323. package/src/core/static-files.ts +40 -0
  324. package/src/core/static-host.ts +125 -0
  325. package/src/core/svg-images.ts +98 -0
  326. package/src/core/syntax-diagnostics.ts +491 -0
  327. package/src/core/types.ts +43 -1
  328. package/src/deploy/artifacts.ts +2 -28
  329. package/src/deploy/generated-files.ts +128 -0
  330. package/src/deploy/platforms/vercel.ts +25 -1
  331. package/src/deploy/redirects.ts +43 -16
  332. package/src/deploy/sitemap.ts +2 -1
  333. package/src/markdown/base-links.ts +87 -6
  334. package/src/markdown/code-title.ts +29 -13
  335. package/src/markdown/content-assets.ts +210 -0
  336. package/src/markdown/directives.ts +69 -5
  337. package/src/markdown/fence-language.ts +106 -0
  338. package/src/markdown/fence-meta.ts +42 -0
  339. package/src/markdown/github-alerts.ts +172 -0
  340. package/src/markdown/heading-anchors.ts +265 -56
  341. package/src/markdown/include.ts +30 -3
  342. package/src/markdown/index.ts +145 -40
  343. package/src/markdown/prompt-text.ts +95 -0
  344. package/src/markdown/relative-links.ts +37 -2
  345. package/src/markdown/route-snapshot.ts +22 -12
  346. package/src/markdown/svg-fallback.ts +51 -0
  347. package/src/markdown/title-heading.ts +134 -0
  348. package/src/migrate/migrate.ts +127 -2
  349. package/src/og/card.ts +14 -4
  350. package/src/openapi/asyncapi.ts +14 -6
  351. package/src/openapi/checks.ts +160 -15
  352. package/src/openapi/model.ts +172 -33
  353. package/src/openapi/parse.ts +13 -4
  354. package/src/openapi/render-mdx.ts +55 -9
  355. package/src/openapi/source.ts +87 -6
  356. package/src/ratelimit/index.ts +2 -0
  357. package/src/ratelimit/memory.ts +1 -1
  358. package/src/ratelimit/runtime.ts +81 -0
  359. package/src/ratelimit/schema.ts +2 -0
  360. package/src/ratelimit/unkey.ts +61 -0
  361. package/src/registry/eject.ts +0 -1
  362. package/src/registry/registry.ts +1 -1
  363. package/src/search/documents.ts +10 -2
  364. package/src/sources/filesystem.ts +28 -3
  365. package/src/sources/mdx-remote.ts +7 -15
  366. package/src/theme/color.ts +14 -0
  367. package/src/theme/entry.ts +22 -1
  368. package/src/theme/palette.ts +74 -1
  369. package/dist/cli/chunk-0pmb4tr4.js.map +0 -35
  370. package/dist/cli/chunk-0w6hs479.js.map +0 -10
  371. package/dist/cli/chunk-150jk7km.js +0 -52
  372. package/dist/cli/chunk-150jk7km.js.map +0 -10
  373. package/dist/cli/chunk-36bxs9c7.js.map +0 -10
  374. package/dist/cli/chunk-53jqw8z0.js +0 -89
  375. package/dist/cli/chunk-53jqw8z0.js.map +0 -10
  376. package/dist/cli/chunk-60hz265r.js.map +0 -10
  377. package/dist/cli/chunk-desvm72z.js.map +0 -14
  378. package/dist/cli/chunk-dpy8qdjf.js.map +0 -17
  379. package/dist/cli/chunk-es8h0v3j.js +0 -441
  380. package/dist/cli/chunk-es8h0v3j.js.map +0 -11
  381. package/dist/cli/chunk-kdpzw38a.js.map +0 -11
  382. package/dist/cli/chunk-nbyvgnnm.js.map +0 -36
  383. package/dist/cli/chunk-s0bdnc7v.js.map +0 -10
  384. package/dist/cli/chunk-twy7psdz.js.map +0 -10
  385. package/dist/cli/chunk-ydp3065p.js +0 -64
  386. package/dist/cli/chunk-ydp3065p.js.map +0 -10
  387. /package/dist/cli/{chunk-y4msrwm9.js.map → chunk-080mfch6.js.map} +0 -0
  388. /package/dist/cli/{chunk-9qzgg6mj.js.map → chunk-34wap3ed.js.map} +0 -0
  389. /package/dist/cli/{chunk-1f882900.js.map → chunk-c5bjxq8v.js.map} +0 -0
  390. /package/dist/cli/{chunk-bxwhtvw8.js.map → chunk-f260jdrn.js.map} +0 -0
  391. /package/dist/cli/{chunk-74wfhv79.js.map → chunk-fnxgtjeq.js.map} +0 -0
  392. /package/dist/cli/{chunk-dhwtqnnd.js.map → chunk-gxhcf2hs.js.map} +0 -0
  393. /package/dist/cli/{chunk-tfycddbt.js.map → chunk-h0qwdbem.js.map} +0 -0
  394. /package/dist/cli/{chunk-j1x92mx9.js.map → chunk-j4nfgn2j.js.map} +0 -0
  395. /package/dist/cli/{chunk-ave1wcbr.js.map → chunk-qcyxkj44.js.map} +0 -0
  396. /package/dist/cli/{chunk-58bt3hag.js.map → chunk-x7bh6d9j.js.map} +0 -0
  397. /package/dist/cli/{chunk-hvcxng5f.js.map → chunk-ydqzkhjn.js.map} +0 -0
@@ -73,7 +73,33 @@ changelog: {
73
73
 
74
74
  Each takes a map of locale code to text too, like a [header tab label](/docs/content/i18n#per-locale-navigation). The index renders in the default locale, so it shows that locale's entry. Leave either unset and it keeps the built-in text, which a [UI string override](/docs/content/i18n#translated-ui) (`changelog.title` and `changelog.description` under `i18n.ui`) replaces.
75
75
 
76
- The page appears only when nothing already occupies the `/changelog` route. To replace it with your own design, add a [custom page](/docs/advanced/custom-pages) at `pages/changelog.astro` — it takes over and Blume stops generating the default index. The `<Update>` component the previous timeline was built from still ships for a custom page that wants inline release notes. It isn't one of the MDX components, so import it in the `.astro` page itself: `import Update from "blume/components/content/Update.astro";`.
76
+ The page appears only when nothing already occupies the `/changelog` route.
77
+
78
+ ### Writing your own index
79
+
80
+ To put something above the list — an introduction, a link to the feed, a note about your release cadence — write the page yourself and place `<Changelog />` where the list belongs. Blume still owns the list, so you only write the parts it has no slot for:
81
+
82
+ ```mdx changelog/index.mdx
83
+ ---
84
+ title: Changelog
85
+ description: Every Acme SDK release, newest first.
86
+ mode: center
87
+ ---
88
+
89
+ Every release, newest first. Subscribe to the [RSS feed](/changelog/rss.xml) to follow along.
90
+
91
+ <Changelog />
92
+ ```
93
+
94
+ A content page at `/changelog` takes over the route, so the generated page steps aside and yours renders instead. Put it at `changelog/index.mdx`, beside your entries, so the sidebar's Changelog group links to it — a `changelog.mdx` next to the folder adds a second Changelog item to the sidebar. [`mode: center`](/docs/content/frontmatter#layout) gives it the generated page's layout — no sidebar, no table of contents, a wider centered column — and the generated page renders the same component, so the two can't drift apart. The list downlevels to Markdown on the agent surfaces (`/changelog.md`, llms-full.txt, MCP `get_page`, search) exactly as the generated page's does.
95
+
96
+ The list is always the default locale's entries, wherever the tag sits — the rule every surface that publishes it already follows, so a translated page lists the same releases rather than disagreeing about which exist. Its dates read in the page's own language.
97
+
98
+ ### Replacing the page entirely
99
+
100
+ For a design the component can't express, add a [custom page](/docs/advanced/custom-pages) at `pages/changelog.astro` — it takes over and Blume stops generating the default index. The `<Update>` component the previous timeline was built from still ships for a custom page that wants inline release notes. It isn't one of the MDX components, so import it in the `.astro` page itself: `import Update from "blume/components/content/Update.astro";`.
101
+
102
+ Building the list yourself means reproducing what `<Changelog />` does, including one thing that fails quietly: entries from a non-filesystem source (such as [GitHub Releases](#from-github-releases)) render through a parallel `staged` collection, so a page that reads `getCollection("docs")` alone lists none of them, with no warning. Prefer `<Changelog />` in a content page unless you genuinely need different markup.
77
103
 
78
104
  ## From GitHub Releases
79
105
 
@@ -15,7 +15,7 @@ npx skills add haydenbleasel/blume
15
15
 
16
16
  ## Migration
17
17
 
18
- `blume-migrate` moves an existing docs site to Blume: Mintlify, Fumadocs, Docusaurus, Starlight, Nextra, or any other framework. It targets idiomatic Blume rather than a line-by-line port, with a mapping reference for each named framework, a redirect for every URL that moves, and a report of anything it drops. The quickest way to run it is [`blume migrate`](/docs/migrating), which detects your framework and opens Codex or Claude Code on the copy bundled in the package. To use it from another agent, install it:
18
+ `blume-migrate` moves an existing docs site to Blume from Mintlify, Docusaurus, GitBook, MkDocs, ReadMe, the [other named sources](/docs/migrating#sources), or any other framework. It targets idiomatic Blume rather than a line-by-line port, with a mapping reference for each named framework, a redirect for every URL that moves, and a report of anything it drops. The quickest way to run it is [`blume migrate`](/docs/migrating), which detects your framework and opens Codex or Claude Code on the copy bundled in the package. To use it from another agent, install it:
19
19
 
20
20
  ```bash
21
21
  npx skills add haydenbleasel/blume --skill blume-migrate
@@ -28,7 +28,7 @@ Findings are grouped by check rather than listed per page, so the report reads a
28
28
  - `--url <origin>` — also probe a live deployment for status codes, response headers, and configured redirects.
29
29
  - `--external` — probe outbound links over the network.
30
30
  - `--ignore <glob>` — with `--external`, don't probe an outbound link whose URL matches the glob, like `--ignore "https://api.acme.example/**"`. Repeat the flag for more patterns. It matches the way [`blume validate --ignore`](/docs/cli/validate#flags) does, and internal links are always checked.
31
- - `--only <check|category>` / `--skip <check|category>` — narrow the report to, or away from, these checks or categories (comma-separated). A term that names no check or category is an error that suggests the closest one, so a typo can't quietly empty the report.
31
+ - `--only <check|category>` / `--skip <check|category>` — narrow the report to, or away from, these checks or categories (comma-separated). The audit count in the summary counts only the checks left in. A term that names no check or category is an error that suggests the closest one, so a typo can't quietly empty the report.
32
32
  - `--list-checks` — print every check the audit can report, then exit.
33
33
  - `--verbose` — list every affected page with each finding's full detail, instead of the first few.
34
34
  - `--json` — emit the report as JSON on stdout.
@@ -15,8 +15,8 @@ blume doctor
15
15
 
16
16
  - **The Node version** against the range the installed `blume` package supports, read from its own `engines` field. A version outside it is a warning: things may work, but it isn't a combination Blume tests.
17
17
  - **`blume.config.ts`**, with the same validation a build runs. A removed or renamed key fails with a hint naming its replacement rather than a bare "unrecognized key".
18
- - **Every content page and folder meta**: the diagnostics `blume dev` and `blume build` print as they load the project — invalid frontmatter, navigation problems, missing include targets, and the rest — collected in one report.
19
- - **Navigation that leads nowhere**: a tab, selector item, featured link, header action, or call to action whose path matches no page, custom `.astro` page, generated route, or reference: a warning (`BLUME_NAV_MISSING_PAGE`), the same one `blume dev` and `blume build` print.
18
+ - **Every content page and folder meta**: the diagnostics `blume dev` and `blume build` print as they load the project — invalid frontmatter, `.mdx` that doesn't [parse](/docs/content#markdown-and-mdx), navigation problems, missing include targets, and the rest — collected in one report.
19
+ - **Navigation that leads nowhere**: a tab, selector item, featured link, header action, or call to action whose path matches no page, custom `.astro` page, generated route, reference, `public/` file, or file Blume generates: a warning (`BLUME_NAV_MISSING_PAGE`), the same one `blume dev` and `blume build` print. A tab is checked where it links: its `href` when it has one, else its `path`.
20
20
  - **[`<Component>`](/docs/content/components#component) examples that don't exist**: a `path` that names no file under `examples`, which the page would show as a "No example found" box: a warning (`BLUME_EXAMPLE_NOT_FOUND`) at its line, as `blume dev` and `blume build` print it. A `components.ts` override or island named `Component` replaces the built-in, and isn't checked.
21
21
  - **Features that need a server** on a site configured for static output — the assistant, the MCP server, the Try it playground's built-in proxy, or server-mode search (Mixedbread): an error naming the feature, with the deployment adapter to switch to (or, when a host adapter is set to `output: "static"`, telling you to drop that option).
22
22
  - **Packages your config needs** that aren't installed — the SDK a search, content source, assistant, or narration adapter imports, a deployment adapter's `@astrojs/*` package, or the renderer for Vue or Svelte islands: an error naming each package, with the install command for your package manager. `blume build` stops on the same check.
@@ -49,7 +49,7 @@ questions:
49
49
  - id: install-node-version
50
50
  question: What is the minimum Node.js version required?
51
51
  expected:
52
- - Node 22.12 or newer
52
+ - Node 22.19 or newer
53
53
  routes: /docs/quickstart
54
54
  - id: deploy-vercel
55
55
  question: How do I deploy to Vercel?
@@ -26,7 +26,7 @@ blume <command> [options]
26
26
  | [`blume translate`](/docs/cli/translate) | Translate docs into the configured locales with a local agent CLI. |
27
27
  | [`blume version [id]`](/docs/cli/version) | Freeze the current docs as an archived version (no id lists configured versions). |
28
28
  | [`blume skill`](/docs/discoverability/agent-discovery#your-sites-skill) | Write your docs' agent skill with Codex or Claude Code, in place of the generated one. |
29
- | [`blume migrate [source]`](/docs/migrating) | Move a Mintlify, Fumadocs, Docusaurus, Starlight, or Nextra site to Blume with Codex or Claude Code. |
29
+ | [`blume migrate [source]`](/docs/migrating) | Move a Mintlify, Docusaurus, GitBook, MkDocs, ReadMe, or other docs site to Blume with Codex or Claude Code. |
30
30
  | [`blume upgrade`](/docs/upgrading) | Move to a new major: bump `blume`, then list the config changes left or hand them to Codex or Claude Code. |
31
31
 
32
32
  ## Common flags
@@ -42,7 +42,7 @@ blume <command> [options]
42
42
  - `blume dev --content-dir <dir>` — scan a different content folder without editing `blume.config.ts`.
43
43
  - `blume dev --debug` — verbose Astro/Vite logging for troubleshooting.
44
44
  - `blume dev --preview` / `blume build --preview` — include drafts and unpublished CMS content.
45
- - `blume build --no-strict` — build despite diagnostic errors. By default `blume build` fails (exit 1) on any error diagnostic, because pages that fail frontmatter validation are dropped from the output; with `--no-strict` the build succeeds and reports how many pages are missing. `blume dev --strict` opts dev into the same fail-fast behavior.
45
+ - `blume build --no-strict` — build despite diagnostic errors. By default `blume build` fails (exit 1) on any error diagnostic, because pages that fail frontmatter validation, and `.mdx` pages that don't [parse](/docs/content#markdown-and-mdx), are dropped from the output; with `--no-strict` the build succeeds and reports how many pages are missing. A failure only the build itself finds, such as a page that throws while it renders, still fails it. `blume dev --strict` opts dev into the same fail-fast behavior.
46
46
  - `blume build --analyze` — print the client JavaScript bundle sizes (largest first) after the build.
47
47
  - `blume build --budget-js <kb> --budget-css <kb>` — fail the build when total client JavaScript/CSS exceeds the budget, turning a performance target into a CI gate.
48
48
  - `blume build --isolated` — build into a throwaway `.blume-verify/` runtime (and its own `dist/`) instead of `.blume/`, so a running `blume dev` server and your real `dist/` are left untouched. See [Verifying while the dev server runs](#verifying-while-the-dev-server-runs).
@@ -107,3 +107,5 @@ Add a `tsconfig.json` extending Astro's config to your project root so authored
107
107
  ```
108
108
 
109
109
  Without a project `tsconfig.json`, only the generated runtime is checked.
110
+
111
+ The build reads this file too, to resolve imports, so an `extends` or `references` entry that doesn't resolve (a package that isn't installed, a file that was moved, a framework's generated tsconfig that doesn't exist yet) stops `blume build`, `blume dev`, and `blume check` with `BLUME_TSCONFIG_EXTENDS`, at the line that names it. Install, restore, or generate the file, or remove the entry.
@@ -9,14 +9,16 @@ description: blume validate checks every link in your content — pages, anchors
9
9
  blume validate
10
10
  ```
11
11
 
12
- - **Internal page links** (`/guides/intro`, `./sibling`) must resolve to a real page — broken ones are reported as errors. Every link form Markdown and MDX render is read: inline links, including one whose label wraps onto the next line; reference-style links, checked at their `[label]: /target` definition; autolinks (`<https://…>`, in `.md`); and a component's string `href` (`<Card href="./install">`). A lowercase HTML `<a href>` ships exactly as written, so it's checked the way a browser reads it: a relative path resolves against the page's URL, and no [`basePath`](/docs/deployment#mount-the-docs-under-a-path) is added. An `href={…}` expression isn't checked.
13
- - **Anchor links** (`#section`, `/guides/intro#setup`) must match an anchor on the target page — a heading's id (generated or [pinned](/docs/content/syntax#custom-anchors)) or a raw HTML element's `id` attribute — with misses reported as warnings. Ids inside code blocks, inline code, HTML comments, and `<Prompt>` blocks don't count.
14
- - **Asset links** are checked where the file lives: an absolute path (`/logo.png`) against the `public/` directory, a relative image embed (`![](./diagram.png)`) against the page's own folder. A plain link to a relative path still resolves as a site route — only image embeds go through the image pipeline.
12
+ - **Internal page links** (`/guides/intro`, `./sibling`) must resolve to a real page — broken ones are reported as errors. Every link form Markdown and MDX render is read: inline links, including one whose label wraps onto the next line; reference-style links, checked at their `[label]: /target` definition (the first one, when a label is defined twice, since that's the one that renders); autolinks (`<https://…>`, in `.md`); and a component's string `href` (`<Card href="./install">`). A root-relative link to a Markdown file in your content (`/guides/setup.md`) is checked at the page that file publishes, where the build points it; one naming no file there is checked as the page's [Markdown copy](/docs/discoverability/markdown#raw-markdown). A link's target is read the way the renderer reads it, so `[x](<https://example.com/a(b>)` points at `https://example.com/a(b` and `![](image\(1\).png)` at `image(1).png`, with any spaces around it and a title in any of its quotes (`"…"`, `'…'`, or `(…)`) after it. Links in code (fenced, including a fence inside a block quote, or inline) and in comments (`<!-- … -->` in `.md`, `{/* … */}` in `.mdx`) never render, so they aren't checked. A lowercase HTML `<a href>` to a page ships as written, so it's checked the way a browser reads it: a relative path resolves against the page's URL, and no [`basePath`](/docs/deployment#mount-the-docs-under-a-path) is added (a root-relative one gains a [deployment `base`](/docs/deployment#subpath-deploys), as every link does). An `href={…}` expression isn't checked.
13
+ - **Anchor links** (`#section`, `/guides/intro#setup`) must match an anchor on the target page — a heading's id (generated or [pinned](/docs/content/syntax#custom-anchors)), a raw HTML element's `id` attribute, or an `<a name>` — with misses reported as warnings. Ids inside code blocks, inline code, HTML comments, and `<Prompt>` blocks don't count.
14
+ - **Other link schemes** (`mailto:`, `tel:`, an app's `vscode:` deep link) aren't checked: whether a reader's browser or an installed app handles one can't be known. ReadMe's own link schemes are the exception: `doc:`, `ref:`, `page:`, `changelog:`, and `blog:` only resolve on ReadMe and ship as dead links anywhere else, so each is reported as a warning. Link the page by its path instead.
15
+ - **Asset links** are checked where the file lives: an absolute path (`/logo.png`) against the `public/` directory, a relative image embed (`![](./diagram.png)`, or a card's `<Card img="./cover.png">`) against the page's own folder. A relative link to any other file (`[spec](./spec.pdf)`, or an `<a href>` or a component's `href`) is checked against the page's own folder too, since Blume [publishes a file there](/docs/content/syntax#links-and-images) with the page; one that names no file beside the page resolves as a site route against `public/`. A media element's `src` (`<img>`, `<source>`, `<video>`, or `<audio>`, as raw HTML in `.md` or an element in `.mdx`) is checked the same way: a relative one is found beside the page, or else where the browser requests it, against `public/`. A project with no `public/` folder ships no public files, so an absolute asset path there is reported missing. An `.html` link (`./setup.html`, `/guides/setup.html`) is a file link too: Blume serves pages at their routes, never at `.html` URLs, so unless `public/` has that file, the link is reported, with the page's route as the fix when a page matches it.
16
+ - **Redirect targets** in [`redirects`](/docs/deployment#redirects) must lead somewhere: an exact internal `to` to a page, a file, or another redirect, and a [pattern](/docs/deployment#pattern-redirects) `to` to a path with something served under its literal start (`/v2/` in `/v2/:slug*`). One that leads nowhere is a warning, located at its `to` in `blume.config.ts`. A link to a redirect's `from` is valid either way. External targets aren't checked.
15
17
  - **External links** are only checked with [`--external`](#flags) (off by default since it requires the network). A dead link is an error: a 404 or 410, or a host name that doesn't resolve. Anything else that fails is a warning, since it's more often the other site or the network than the link: any other error status (a 401 or 403 from a site that turns bots away, a 429 rate limit, a 5xx outage), a refused or dropped connection, or no answer within 10 seconds. To leave out URLs that can't be checked from where the command runs, like a placeholder domain, a local server, or a site that turns bots away, pass [`--ignore`](#flags). A bare URL that GitHub-flavored Markdown links on its own, with no link syntax around it, isn't read; [`blume audit --external`](/docs/cli/audit) checks it in the built site.
16
18
 
17
19
  ## What counts as a page
18
20
 
19
- Links resolve against everything the built site serves, not just Markdown pages: custom [`.astro` pages](/docs/advanced/custom-pages), the generated [changelog](/docs/advanced/changelog) index, every configured [redirect](/docs/deployment#redirects), and, on a [multi-language site](/docs/content/i18n), an untranslated page's fallback URL under each locale. A page that fails to parse is reported alongside the link findings, because a page that never loads is a link-validation blind spot rather than a clean result.
21
+ Links resolve against everything the built site serves, not just Markdown pages: custom [`.astro` pages](/docs/advanced/custom-pages), [API reference](/docs/references/openapi) pages, the generated [changelog](/docs/advanced/changelog) index, every configured [redirect](/docs/deployment#redirects), and, on a [multi-language site](/docs/content/i18n), an untranslated page's fallback URL under each locale. The files Blume writes beside your pages count too, while the config has them on: `/llms.txt` and `/llms-full.txt`, the sitemap, `/robots.txt`, the RSS feeds, `/openapi.json`, `/agent-readability.json`, `/skill.md`, and the `.well-known` discovery files such as `/.well-known/agent-skills/index.json`. So does a folder in `public/` with an `index.html`, at `/demo` or `/demo/` for `public/demo/index.html`, as a static host serves it. A page that fails to load, with invalid front matter or `.mdx` that doesn't [parse](/docs/content#markdown-and-mdx) (`BLUME_MDX_SYNTAX`), is reported alongside the link findings, because a page that never loads is a link-validation blind spot rather than a clean result.
20
22
 
21
23
  ## Flags
22
24
 
@@ -29,10 +31,11 @@ Links resolve against everything the built site serves, not just Markdown pages:
29
31
 
30
32
  | Code | Severity | Meaning |
31
33
  | --- | --- | --- |
32
- | `BLUME_BROKEN_LINK` | error | An internal link points at a route no page serves. |
34
+ | `BLUME_BROKEN_LINK` | error | An internal link points at a route no page serves. When it's a relative link to a file that exists but whose name, or a folder's, starts with `_`, the fix says so: that file is a [partial](/docs/content/includes#partials), which isn't published. |
33
35
  | `BLUME_BROKEN_ANCHOR` | warning | The page exists but has no anchor matching the fragment. |
34
- | `BLUME_BROKEN_ASSET` | warning | An absolute path isn't in `public/`, or a relative image embed isn't beside the page. |
35
- | `BLUME_ASSETS_UNCHECKED` | info | There is no `public/` directory, so absolute asset paths weren't checked. |
36
+ | `BLUME_BROKEN_ASSET` | warning | An absolute path isn't in `public/`, a relative image embed isn't beside the page, an `.html` link names no file in `public/`, or a media `src` names a file the site doesn't serve at that URL. |
37
+ | `BLUME_BROKEN_REDIRECT` | warning | A redirect's internal `to` leads to no page, file, or other redirect. |
38
+ | `BLUME_UNSUPPORTED_LINK_SCHEME` | warning | A link uses one of ReadMe's link schemes (`doc:`, `ref:`, `page:`, `changelog:`, `blog:`), which browsers can't follow. |
36
39
  | `BLUME_DEAD_LINK` | error or warning | An external link (with `--external`) returned 404/410 or its host doesn't resolve (error), or it got any other error status, no connection, or no answer in time (warning). |
37
40
 
38
41
  The exit code is the contract: errors exit non-zero, and `--strict` makes warnings do the same.
@@ -393,6 +393,8 @@ analytics: [
393
393
 
394
394
  `attributes` is a map of extra HTML attributes (`data-*`, `id`, …) spread onto the tag, and `strategy` adds `async` or `defer` to external scripts.
395
395
 
396
+ A root-relative `src` points at a file in `public/` (`src: "/js/redirects.js"` for `public/js/redirects.js`), so under a [`deployment.base`](/docs/deployment#subpath-deploys) it gets the base, like a link does: with `base: "/docs"`, the tag loads `/docs/js/redirects.js`. A `src` that already starts with the base is left as written, and an absolute or protocol-relative URL passes through, so a script served outside the site's base takes its full URL.
397
+
396
398
  ## Options
397
399
 
398
400
  | Adapter | Option | Default | Description |
@@ -22,6 +22,8 @@ export default defineComponents({
22
22
 
23
23
  Keys are the names you write in MDX (`<Callout>`, `<Pricing>`). Use the `.tsx` filename when you import React components.
24
24
 
25
+ A capitalized tag that's neither a built-in, an [island](/docs/content/islands), nor an override fails the page when it renders, so `blume dev`, `blume build`, and `blume check` warn about it first as `BLUME_UNKNOWN_COMPONENT`, once per tag, naming every page that uses it.
26
+
25
27
  ### Reference form
26
28
 
27
29
  Every override — in `mdx` or `layout` — accepts three forms:
@@ -104,7 +106,7 @@ Wired slots (names are case-sensitive; any other key under `layout` renders nowh
104
106
 
105
107
  The `mobile` variant also receives the [page actions](/docs/content/navigation#page-actions) as its children (an empty element the actions move into from the right rail below 1,280px), so render a `<slot />` in it to keep them on narrower screens. It renders on a page with no headings as well, with `headings` empty, so the actions still show there.
106
108
 
107
- `PageHeader` and `PageFooter` have no built-in component — they render nothing until you set them, which makes them handy injection points for a promo banner or a "last updated" note. The built-in `Footer` holds the only links to your repository and social profiles, so a `Footer` override (the place for a marketing footer the config can't express) should keep them. With `consent` set, also keep a way to reopen it: any element with `data-blume-consent-open` works as the [Cookie settings](/docs/configuration/consent#changing-an-answer) link.
109
+ `PageHeader` and `PageFooter` have no built-in component — they render nothing until you set them, which makes them handy injection points for a promo banner or a "last updated" note. `PageFooter` renders at the foot of every page Blume builds from your content, API operation pages included, with `Feedback` and `Pagination` after it. A page in [`custom` or `frame` mode](/docs/content/frontmatter#layout) drops that page-end area along with its title and breadcrumbs. A [custom page](/docs/advanced/custom-pages) under `pages/` renders the layout it wraps itself in: `RootLayout` takes slot overrides through its `layout` prop, and `PageLayout` has no page-end area. The built-in `Footer` holds the only links to your repository and social profiles, so a `Footer` override (the place for a marketing footer the config can't express) should keep them. With `consent` set, also keep a way to reopen it: any element with `data-blume-consent-open` works as the [Cookie settings](/docs/configuration/consent#changing-an-answer) link.
108
110
 
109
111
  Layout slots accept the same [three reference forms](#reference-form) as MDX overrides, so a slot can be a path string or a hydrated descriptor (`{ component, client }`) when you want an interactive header or footer.
110
112
 
@@ -101,7 +101,7 @@ export default defineConfig({
101
101
  | `description` | — | Default meta description, used for SEO and OG. |
102
102
  | `logo` | — | Brand mark and/or wordmark shown in the header. |
103
103
  | `banner` | — | Site-wide announcement bar above the header. |
104
- | `footer` | — | A row of links and social profile icons below the content. |
104
+ | `footer` | — | A copyright line, a row of links, and social profile icons below the content. |
105
105
 
106
106
  ### Logo
107
107
 
@@ -204,10 +204,11 @@ A translated banner keys dismissal on the default locale's text, so one dismissa
204
204
 
205
205
  ### Footer
206
206
 
207
- Every page ends with a footer: one row, as tall as the header, with your links on one side and social profile icons on the other. The footer is the only place Blume links your social profiles and repository, so the [`github`](#github) repository comes first among the icons whenever it's set. `footer` adds the rest:
207
+ Every page ends with a footer: one row, as tall as the header, with your links on one side, social profile icons on the other, and a copyright line centered between them. The footer is the only place Blume links your social profiles and repository, so the [`github`](#github) repository comes first among the icons whenever it's set. `footer` adds the rest:
208
208
 
209
209
  ```ts blume.config.ts lineNumbers
210
210
  footer: {
211
+ copyright: "© 2026 Acme, Inc.",
211
212
  links: [
212
213
  { label: "Pricing", href: "https://acme.dev/pricing" },
213
214
  { label: "Changelog", href: "/changelog" },
@@ -220,9 +221,9 @@ footer: {
220
221
  },
221
222
  ```
222
223
 
223
- `links` appear in the order you write them. Write internal links as if mounted at root; an external link opens in a new tab. On a [multilingual](/docs/content/i18n#per-locale-navigation) site, a `label` can be a map of locale code to text, and an internal link moves into the reader's locale whenever that locale serves the page. `socials` maps a platform to its profile URL, and the icons appear in the order you write them. The platforms are `bluesky`, `discord`, `facebook`, `github`, `hacker-news`, `instagram`, `linkedin`, `medium`, `podcast`, `reddit`, `slack`, `telegram`, `threads`, `website`, `x`, and `youtube`. A `github` social replaces the repository link; hide that link with [`navigation.repo`](/docs/content/navigation#repository-link).
224
+ `copyright` is plain text, so write `©` itself rather than `&copy;`. For a year that keeps itself current, use a template literal: `` `© ${new Date().getFullYear()} Acme, Inc.` ``. `links` appear in the order you write them. Write internal links as if mounted at root; an external link opens in a new tab. On a [multilingual](/docs/content/i18n#per-locale-navigation) site, `copyright` and a `label` can each be a map of locale code to text, and an internal link moves into the reader's locale whenever that locale serves the page. `socials` maps a platform to its profile URL, and the icons appear in the order you write them. The platforms are `bluesky`, `discord`, `facebook`, `github`, `hacker-news`, `instagram`, `linkedin`, `medium`, `podcast`, `reddit`, `slack`, `telegram`, `threads`, `website`, `x`, and `youtube`. A `github` social replaces the repository link; hide that link with [`navigation.repo`](/docs/content/navigation#repository-link).
224
225
 
225
- A site with no links, profiles, or repository has no footer. Custom pages built on `PageLayout` show it too, unless they fill the layout's own `footer` slot. To replace it entirely, override the [`Footer` layout slot](/docs/configuration/customization#layout-slots).
226
+ A site with no copyright, links, profiles, or repository has no footer. Custom pages built on `PageLayout` show it too, unless they fill the layout's own `footer` slot. To replace it entirely, override the [`Footer` layout slot](/docs/configuration/customization#layout-slots).
226
227
 
227
228
  ## Content
228
229
 
@@ -238,7 +239,7 @@ content: {
238
239
  | --- | --- | --- |
239
240
  | `root` | `"docs"` | Folder Blume scans for content. Shorthand for a single `filesystem()` source; not allowed beside `sources`. |
240
241
  | `include` | `["**/*.{md,mdx}"]` | Globs that match content files. Shorthand, like `root`. |
241
- | `exclude` | `["**/_*", "**/.*"]` | Globs to ignore (underscore- and dot-files). Shorthand, like `root`. |
242
+ | `exclude` | `["**/_*", "**/.*"]` | Globs to ignore. Yours add to the defaults, which skip underscore- and dot-files; a `!` entry drops a pattern, so `"!**/_*"` publishes `_`-prefixed files. Shorthand, like `root`. |
242
243
  | `sources` | one `filesystem()` | Content source adapters from `blume/sources`, replacing the shorthand. See [Content sources](/docs/content/sources). |
243
244
  | `pages` | `"pages"` | Folder for custom `.astro` pages. |
244
245
  | `defaultType` | `"doc"` | Page `type` used when frontmatter omits it. |
@@ -365,7 +366,7 @@ lastModified: "git",
365
366
  | `"git"` | Read the date from git history (commit dates). |
366
367
  | `"frontmatter"` | Never run git — use only the `lastModified` frontmatter field. |
367
368
 
368
- The git source reads the most recent commit that touched each file, so it works in any git repository — including monorepos — and needs the repo's history at build time. CI platforms usually check out a shallow clone, which silently drops most dates (the build warns with `BLUME_SHALLOW_GIT_HISTORY` when that happens): on Vercel, set the `VERCEL_DEEP_CLONE=true` environment variable; with `actions/checkout`, set `fetch-depth: 0`. A page's own `lastModified` frontmatter always wins, which is handy for pinning a date or for files that aren't committed yet:
369
+ The git source reads the most recent commit that touched each file, so it works in any git repository — including monorepos — and needs the repo's history at build time. It follows renames, as `git log --follow` does: renaming `page.md` to `page.mdx`, or moving a file to another folder under the same content root, keeps the page's date as long as the commit leaves its content unchanged. A rename that also edits the file dates the page at that commit, and so does moving a file in from outside the content root. CI platforms usually check out a shallow clone, which silently drops most dates (the build warns with `BLUME_SHALLOW_GIT_HISTORY` when that happens): on Vercel, set the `VERCEL_DEEP_CLONE=true` environment variable; with `actions/checkout`, set `fetch-depth: 0`. A page's own `lastModified` frontmatter always wins, which is handy for pinning a date or for files that aren't committed yet:
369
370
 
370
371
  ```mdx page.mdx
371
372
  ---
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  title: Rate limiting
3
- description: Limit how often one reader can call the assistant, the API playground proxy, and server-side search, on by default, with the count kept in memory, Upstash, or Cloudflare.
3
+ description: Limit how often one reader can call the assistant, the API playground proxy, and server-side search, on by default, with the count kept in memory, Upstash, Cloudflare, or Unkey.
4
4
  ---
5
5
 
6
6
  The server routes a reader can call cost you something each time: the [assistant](/docs/configuration/assistant) spends model tokens, the [API playground](/docs/references/openapi#cors-and-the-proxy) proxy relays requests to your API, and [Mixedbread search](/docs/configuration/search#mixedbread) runs a paid query. Blume limits how often one reader, identified by IP address, can call each of them. A reader over the limit gets `429 Too Many Requests` with a `Retry-After` header, and the assistant tells them to try again in a few minutes.
@@ -25,6 +25,7 @@ Each route keeps its own count, so a busy playground session doesn't use up the
25
25
  | `memory()` | The server's memory (the default) | Exactly on one server; per instance on serverless hosts |
26
26
  | `upstash()` | [Upstash Redis](https://upstash.com) | Exactly, on every host |
27
27
  | `cloudflare()` | Cloudflare's Workers rate limiting | Per Cloudflare location |
28
+ | `unkey()` | [Unkey](https://unkey.com) | On every host; regions catch up within moments |
28
29
 
29
30
  ### Memory
30
31
 
@@ -68,6 +69,24 @@ export default defineConfig({
68
69
 
69
70
  The binding's namespace comes from the Worker's name, so two sites on one account don't share a count. Set `namespaceId` to choose it yourself.
70
71
 
72
+ ### Unkey
73
+
74
+ `unkey()` keeps the count in [Unkey's rate limiting](https://www.unkey.com/docs/api-management/ratelimiting/overview), which every instance shares. Create a root key with the `ratelimit.*.limit` permission, plus `ratelimit.*.create_namespace` so Unkey can create the namespace on the first request, then set it as `UNKEY_ROOT_KEY`. Blume talks to Unkey's REST API, so there's nothing to install. Until the key is set, the routes count in memory and `blume build` warns.
75
+
76
+ ```ts blume.config.ts lineNumbers
77
+ import { unkey } from "blume/ratelimit";
78
+
79
+ rateLimit: unkey(),
80
+ ```
81
+
82
+ The namespace is `docs` unless you name another with `namespace`. Name the variable that holds the key with `rootKeyEnv`:
83
+
84
+ ```ts blume.config.ts lineNumbers
85
+ rateLimit: unkey({ namespace: "acme-docs", rootKeyEnv: "DOCS_UNKEY_ROOT_KEY" }),
86
+ ```
87
+
88
+ Unkey counts in each region and shares the counts between regions within moments, so a short burst split across regions can get a few more requests through than the limit. Every count fails with a key Unkey refuses, like one missing those permissions, and a request a shared store fails to count is let through, so if the server logs `Rate limiting failed`, check the key.
89
+
71
90
  ## Behind a reverse proxy
72
91
 
73
92
  Vercel, Netlify, and Cloudflare tell Blume each reader's address. A `node()` server behind a reverse proxy, like nginx or Caddy, sees every request come from the proxy, so every reader shares one count and a busy site hits the limit for everyone. List the host the proxy answers for in `allowedDomains`:
@@ -36,6 +36,8 @@ Named presets: `blue` (default), `green`, `orange`, `pink`, `purple`, `red`, and
36
36
 
37
37
  A string applies to both color modes; pass an object for [a different accent per mode](#dark-mode-colors).
38
38
 
39
+ A value that's neither a preset nor a CSS color, like `"deep purple"` or a hex value without its `#`, warns `BLUME_THEME_COLOR_INVALID` at the line in `blume.config.ts` that sets it, since browsers ignore the styles that use it. The same check covers `action`, `background`, and the [Open Graph card's](/docs/discoverability/open-graph) `seo.og.palette`. Custom properties (`var(--brand)`), `color-mix()`, and the other color functions pass as written.
40
+
39
41
  Text on an accent or action fill, like a button label or a step number, is white while white meets AA on that color, and dark otherwise. A light accent gets dark labels without any extra config. [`blume audit`](/docs/cli/audit#theme-contrast-low) checks a custom accent, action, or background against the same bar.
40
42
 
41
43
  ### Radius
@@ -231,6 +233,8 @@ Set a token under `:root` for light mode and under `:root[data-theme="dark"]` fo
231
233
  | `--blume-code-highlight`, `--blume-code-highlight-border` | Background and left rule of a highlighted code line (`// [!code highlight]` or a `{1,4-5}` range) |
232
234
  | `--blume-code-add`, `--blume-code-add-border` | Background and left rule of an added line (`// [!code ++]`) |
233
235
  | `--blume-code-remove`, `--blume-code-remove-border` | Background and left rule of a removed line (`// [!code --]`) |
236
+ | `--blume-code-error`, `--blume-code-error-border` | Background and left rule of an error line (`// [!code error]`) |
237
+ | `--blume-code-warning`, `--blume-code-warning-border` | Background and left rule of a warning line (`// [!code warning]`) |
234
238
  | `--blume-code-word`, `--blume-code-word-border` | Background and outline of a highlighted word (`// [!code word:…]`) |
235
239
  | `--blume-content-width` | Max width of the prose column — article, breadcrumb, table of contents, feedback, and pagination (default `42rem`) |
236
240
  | `--blume-radius` | Corner radius |
@@ -248,7 +252,7 @@ Set a `--blume-font-*` token to any font stack to use a font outside the curated
248
252
 
249
253
  ## Tailwind utilities
250
254
 
251
- Blume's theme is built with Tailwind v4 internally, and your project's `.astro`, `.tsx`, and `.jsx` files are scanned too — so you can style custom components and pages with utility classes, no Tailwind setup required. Every token is exposed as a utility, so your components track the theme automatically:
255
+ Blume's theme is built with Tailwind v4 internally, and your project's `.astro`, `.jsx`, `.mdx`, `.ts`, and `.tsx` files are scanned too — so you can style custom components and pages with utility classes, no Tailwind setup required. Every token is exposed as a utility, so your components track the theme automatically:
252
256
 
253
257
  | Token | Utilities |
254
258
  | --------------------------- | -------------------------- |
@@ -5,6 +5,8 @@ description: Cards, steps, tabs, accordions, badges, code groups, frames, trees,
5
5
 
6
6
  Blume ships an accessible, themeable component set available in any `.mdx` page with **no imports**. Each one is shown below with a live preview and its source. Components are vanilla and React-free; React switches on only when your project uses it — any `.tsx` or `.jsx` file in the project, a React [island](/docs/content/islands), [`<Component>`](#component) example, or [component override](/docs/configuration/customization) — or when the [assistant](/docs/configuration/assistant) is enabled.
7
7
 
8
+ A component ignores props it doesn't take. Most show their children, so other docs tools' spellings can render empty: VitePress's `<Badge type="tip" text="beta" />` is a Badge with no children, which shows nothing. Blume warns about a childless component given props it doesn't take as `BLUME_UNKNOWN_PROP`, with the props it does take. Write the content between the tags instead: `<Badge>beta</Badge>`.
9
+
8
10
  ## Card and CardGroup
9
11
 
10
12
  Cards link to a destination with an icon, title, and short blurb. Group them with `CardGroup` for a responsive grid. Reach for them on landing pages, section indexes, and “next steps” — anywhere you’re guiding the reader onward.
@@ -31,7 +33,7 @@ Cards link to a destination with an icon, title, and short blurb. Group them wit
31
33
 
32
34
  `Card` takes `title`, an optional `href` (omit it for a non-clickable card), and an `icon` from Blume's built-in icon set. `CardGroup` takes `cols` (default `2`).
33
35
 
34
- A card also takes `img`, an image across its top (or beside the text from the `sm` breakpoint up, with `horizontal`); `cta`, a call-to-action line in the accent color beneath the text; `arrow`, an arrow after the title and the CTA (shown by default only for external links); `type`, which tints the card and picks a matching icon the way a callout does (`note`, `info`, `tip`, `check`, `warning`, or `danger`); and `color`, any CSS color for the icon.
36
+ A card also takes `img`, an image across its top (or beside the text from the `sm` breakpoint up, with `horizontal`), given like a Markdown image's path: a file next to the page (`./cover.png`), a path under `public/`, or a URL; `cta`, a call-to-action line in the accent color beneath the text; `arrow`, an arrow after the title and the CTA (shown by default only for external links); `type`, which tints the card and picks a matching icon the way a callout does (`note`, `info`, `tip`, `check`, `warning`, or `danger`); and `color`, any CSS color for the icon.
35
37
 
36
38
  ## Steps
37
39
 
@@ -119,6 +121,22 @@ pip install acme-commerce
119
121
 
120
122
  The first view shows until the reader picks another. Blume remembers that pick across pages written in views, and writes it to the URL as `?view=Python`, so a link opens on the same view. A link to a heading inside a view opens that view. Use `<View>` when most of the page changes with the choice, and [Tabs](#tabs) when only one part of the page does. The agent-facing Markdown includes every view, each under its name.
121
123
 
124
+ ## Callout
125
+
126
+ `<Callout>` is the component behind [callout directives](/docs/content/syntax#callouts): `:::warning[Heads up]` renders `<Callout type="warning" title="Heads up">`. Write the component itself when a callout needs its own icon or color, which a directive can't set.
127
+
128
+ <Callout type="tip" title="Ship it" icon="rocket">
129
+ Run `blume build` before you deploy.
130
+ </Callout>
131
+
132
+ ```astro
133
+ <Callout type="tip" title="Ship it" icon="rocket">
134
+ Run `blume build` before you deploy.
135
+ </Callout>
136
+ ```
137
+
138
+ `type` sets the tint and the default icon: `note`, `info` (the default), `tip`, `success` (or `check`), `warning`, or `danger`. The aliases `caution`, `error`, `important`, and `warn` work only in the `:::` form. `title` adds a bold first line. `icon` replaces the type's icon and takes the same inputs as [`<Icon>`](#icon): a Lucide name, an image path or URL, or an inline SVG. `color`, any CSS color, replaces the type's tint: the border, the icon, and the body text take that color over the page background.
139
+
122
140
  ## Badge
123
141
 
124
142
  A small inline label for status or metadata — version tags, “new” or “beta” markers, stability levels. The `variant` tunes the color to the meaning.
@@ -225,7 +243,7 @@ Illustrate a project or folder layout. Wrap a normal Markdown list and Blume sty
225
243
 
226
244
  ## Accordion
227
245
 
228
- Stack related collapsibles in a single bordered container with dividers between them — FAQs, optional steps, or long examples. Each child is an `AccordionItem` (`title`, optional `icon`, `description`, `defaultOpen`). For a single standalone disclosure, use [Expandable](#expandable).
246
+ Stack related collapsibles in a single bordered container with dividers between them — FAQs, optional steps, or long examples. Each child is an `AccordionItem` (`title`, optional `icon`, `description`, `defaultOpen`). The `title` renders inline Markdown, so code spans, emphasis, and links in it show as they would in a sentence; raw HTML in it shows as text. For a single standalone disclosure, use [Expandable](#expandable).
229
247
 
230
248
  <Accordion>
231
249
  <AccordionItem title="Does it support MDX?">
@@ -249,7 +267,7 @@ Stack related collapsibles in a single bordered container with dividers between
249
267
 
250
268
  ## Expandable
251
269
 
252
- A lightweight inline disclosure for nested detail — expanding a field's sub-properties or an optional aside. `title` labels the toggle (defaults to “Show more”); set `defaultOpen` to start expanded.
270
+ A lightweight inline disclosure for nested detail — expanding a field's sub-properties or an optional aside. `title` labels the toggle (defaults to “Show more”) and renders inline Markdown, as an accordion item's does; set `defaultOpen` to start expanded.
253
271
 
254
272
  <Expandable title="Show advanced options">
255
273
  These settings are optional and rarely need changing.
@@ -295,7 +313,7 @@ Lay cards or blocks out in a responsive grid of equal columns that reflows on mo
295
313
 
296
314
  ## CodeGroup
297
315
 
298
- Group several code blocks into one tabbed switcher — a tab per language or file. The tab label is each block's title (the text after the language), and the group's copy button sits in the tab bar and copies the block that is showing. Add `dropdown` to switch with a menu instead of a tab bar.
316
+ Group several code blocks into one tabbed switcher — a tab per language or file. The tab label is each block's title (the text after the language), or its language's name (`TypeScript`, `Python`) for a block without one, and the group's copy button sits in the tab bar and copies the block that is showing. Add `dropdown` to switch with a menu instead of a tab bar.
299
317
 
300
318
  <CodeGroup>
301
319
 
@@ -367,7 +385,7 @@ Wrap an image or any visual in a centered, bordered frame with an optional `capt
367
385
 
368
386
  ## YouTube
369
387
 
370
- Embed a YouTube video in a responsive, privacy-friendly (`youtube-nocookie.com`) 16:9 frame that ships no client JavaScript. Pass a video `id` or a full `url`, plus an optional `title` (for accessibility) and a `start` time in seconds.
388
+ Embed a YouTube video in a responsive, privacy-friendly (`youtube-nocookie.com`) 16:9 frame that ships no client JavaScript. Pass a video `id` or a full `url`, plus an optional `title` (for accessibility) and a `start` time in seconds. A playlist `url` (`youtube.com/playlist?list=…` or `youtube.com/embed/videoseries?list=…`) embeds the whole playlist.
371
389
 
372
390
  <YouTube id="aqz-KE-bpKQ" title="Big Buck Bunny" />
373
391
 
@@ -468,7 +486,7 @@ A clickable preview that leads with a visual — an icon or image — above a ti
468
486
 
469
487
  ## Prompt
470
488
 
471
- A single row with a label and a copy button. The `description` (Markdown) is the visible label; the body is the prompt itself — hidden, and copied to the clipboard as Markdown, with its links, lists, and code intact, when the **Copy prompt** button is pressed. `actions` controls the buttons (e.g. `["copy", "cursor"]`).
489
+ A single row with a label and a copy button. The `description` (Markdown) is the visible label; the body is the prompt itself — hidden, and copied to the clipboard as Markdown, with its links, lists, and code intact, when the **Copy prompt** button is pressed. Its punctuation copies as you typed it: straight quotes, `--`, and `...` stay as written, though prose elsewhere on the page shows them as curly quotes, dashes, and ellipses. `actions` controls the buttons (e.g. `["copy", "cursor"]`).
472
490
 
473
491
  <Prompt
474
492
  description="Ask the model to **document** an endpoint."
@@ -781,6 +799,8 @@ import CodeBlock from "blume/components/content/CodeBlock.astro";
781
799
 
782
800
  `title` sets the header label — a filename, say — which otherwise shows the language, and `icons={false}` hides the language's brand icon, as [`markdown.code.icons`](/docs/content/syntax#code-blocks) does for fences.
783
801
 
802
+ Without `code`, `CodeBlock` renders its children as written, so a fenced block wrapped in `<CodeBlock>…</CodeBlock>` (the way Fern and Mintlify write code) shows as that fence. `title` and the other props don't apply to it then: give the fence its [title](/docs/content/syntax#code-blocks) after the language instead.
803
+
784
804
  To highlight to an HTML string yourself (e.g. inside your own component), import the underlying helper from `blume/markdown`:
785
805
 
786
806
  ```ts
@@ -148,7 +148,7 @@ sidebar:
148
148
  display: page
149
149
  ```
150
150
 
151
- `hidden` removes the page from the sidebar and from previous/next pagination. On a folder's `index` page it removes only the page's own row: the group row keeps linking to the page, and previous/next links still pass through it.
151
+ `hidden` removes the page from the sidebar and from previous/next pagination, and so from the sitemap, search, and `llms.txt` (see [Hidden pages](/docs/content/navigation#hidden-pages)). On a folder's `index` page it removes only the page's own row: the group row keeps linking to the page, so previous/next links still pass through it and the page stays in the sitemap, search, and `llms.txt`. On the home page, the root `index` page, it also removes only the row: the page stays in the sitemap, search, and `llms.txt`.
152
152
 
153
153
  `display` sets the render mode of the page's folder group ([per-group overrides](/docs/content/navigation#per-group-overrides)) and is only meaningful on a folder's `index` page under the generated sidebar — anywhere else (a non-index page, the content root's own `index` page, or any page under an explicit `navigation.sidebar`) it has no group to configure, and Blume warns with `BLUME_SIDEBAR_DISPLAY_IGNORED`.
154
154
 
@@ -85,7 +85,7 @@ i18n: {
85
85
 
86
86
  Each language gets its own sidebar, built from that locale's files — so translations can diverge in structure, ordering, or labels. Folder [`meta.ts`](/docs/content/meta) files resolve per locale, too: under the default `dir` parser, put a `meta.ts` under `fr/guides/` to order the French group independently. Until a locale has one (or a shared `meta.$.ts`), its group mirrors the [fallback](#fallbacks) locale's — that folder's `meta.ts` and the `sidebar.display` its index page sets — so pages that fall back keep the same titles, order, and collapsible groups. Under the `dot` parser translations sit next to the originals, so a folder's `meta.ts` applies to every locale. Everything else about [navigation](/docs/content/navigation) works the same, per language.
87
87
 
88
- Header tabs, header links, and the footer are configured, not derived from content, so their labels localize in `blume.config.ts`. Every configured label accepts a per-locale map (`{ en: "Docs", fr: "Documentation" }`) alongside the plain-string form, falling back to the default locale's entry for locales you haven't filled in: tab and dropdown labels ([Tabs](/docs/content/navigation#tabs)), [featured links](/docs/content/navigation#featured-links), [header actions](/docs/content/navigation#header-actions) and the call to action, [footer](/docs/configuration#footer) links, and the [banner](/docs/configuration#banner)'s text and link text.
88
+ Header tabs, header links, and the footer are configured, not derived from content, so their labels localize in `blume.config.ts`. Every configured label accepts a per-locale map (`{ en: "Docs", fr: "Documentation" }`) alongside the plain-string form, falling back to the default locale's entry for locales you haven't filled in: tab and dropdown labels ([Tabs](/docs/content/navigation#tabs)), [featured links](/docs/content/navigation#featured-links), [header actions](/docs/content/navigation#header-actions) and the call to action, [footer](/docs/configuration#footer) links, the [banner](/docs/configuration#banner)'s text and link text, and custom clients in the [Connect to MCP](/docs/discoverability/mcp#connect-to-mcp-menu) menu.
89
89
 
90
90
  ```ts blume.config.ts lineNumbers
91
91
  navigation: {
@@ -23,7 +23,7 @@ Here it is live — this next callout is spliced from a shared snippet:
23
23
 
24
24
  ## Partials
25
25
 
26
- Any file whose name (or folder) starts with an underscore is excluded from routing, navigation, search, and sitemaps by default — that convention is the natural home for shared snippets:
26
+ Any file whose name (or folder) starts with an underscore is excluded from routing, navigation, search, and sitemaps — that convention is the natural home for shared snippets. Patterns you add to [`content.exclude`](/docs/configuration#content) add to this rule rather than replacing it; to publish underscore files after all, add `"!**/_*"` to `exclude`:
27
27
 
28
28
  ```text
29
29
  docs/
@@ -37,7 +37,9 @@ docs/
37
37
 
38
38
  A partial is a normal Markdown or MDX file. Its front matter is stripped when spliced (the including page's front matter wins), and everything else — callouts, code fences, components, math — renders exactly as it would inline. Partials can include other partials; a circular include is reported as an error.
39
39
 
40
- Relative image references inside a partial keep working: they're rebased onto the including page, so a colocated `![diagram](./diagram.png)` next to the partial resolves wherever the partial is spliced.
40
+ Link reference definitions (`[pep 508]: https://peps.python.org/pep-0508/`) cross includes too, so a partial can hold a site's shared link definitions: the page and its other partials can cite a partial's (`[PEP 508]`), and a partial can cite the page's. When the page and a partial both define a label, each uses its own.
41
+
42
+ Relative image references inside a partial keep working: they're rebased onto the including page, so a colocated `![diagram](./diagram.png)` or `<Card img="./cover.png">` next to the partial resolves wherever the partial is spliced. An image's path is rebased in whatever form it's written: in angle brackets (`![diagram](<./my diagram.png>)`), with escaped parentheses, or followed by a title in any of its quotes. A link to another file next to the partial (`[spec](./spec.pdf)`, or an `<img src>` or `<a href>` naming one) moves with it too. A link to a page resolves from the including page, as if it were written there.
41
43
 
42
44
  Editing a partial while `blume dev` is running reloads every page that includes it.
43
45
 
@@ -77,6 +79,8 @@ Targets must live inside your content root: a file outside it would be silently
77
79
 
78
80
  Broken links inside a partial are reported against the partial file, not the pages that splice it, so you fix them where they live.
79
81
 
82
+ A partial is read in the including page's format, so a `.md` partial that an `.mdx` page includes is read as MDX. MDX has no HTML comments, so the partial's `<!-- … -->` comments are spliced as MDX comments (`{/* … */}`) and stay hidden. Other HTML there has to be valid JSX: an `<img …>` or `<br>` without a closing slash takes in everything after it in the partial, with no error. Blume warns about it as `BLUME_MDX_UNCLOSED_ELEMENT` at the partial's line; close it as `<br />`. Anything else in the partial that MDX can't parse once it's spliced in, like a stray `{`, is a [`BLUME_MDX_SYNTAX`](/docs/content#markdown-and-mdx) warning at the partial's line, and when it fails the build, the build reports it there too, not at the page that includes it.
83
+
80
84
  :::note
81
85
  Partials are shared across locales and are not translated by `blume translate` — keep partials language-neutral (code, tables, diagrams), or create per-locale partials and include them from each locale's pages.
82
86
  :::
@@ -16,6 +16,10 @@ Blume renders two kinds of file:
16
16
 
17
17
  Reach for `.md` when a page is just prose, and `.mdx` when it needs components or directives. Switching is as simple as renaming the file.
18
18
 
19
+ In `.mdx`, `{` starts a JavaScript expression and `<` a JSX tag, so text that's fine in `.md` can break a page: a literal brace, an HTML comment (write `{/* … */}`), or an element left open (write `<br />`). Blume parses every `.mdx` page when it reads the project, so `blume dev`, `blume validate`, `blume doctor`, and `blume check` report one that doesn't parse as `BLUME_MDX_SYNTAX`, an error at the line and column where MDX stopped, and `blume build` stops there before it compiles anything. With `--no-strict`, the build leaves the page out and builds the rest of the site. `blume dev` keeps the page, and opening it shows the error.
20
+
21
+ An expression can parse and still fail when the page renders, like `{user.name}` on a page where nothing defines `user`. `blume build` reports that as `BLUME_MDX_UNDEFINED_NAME` at the expression's line, in the page or in the [partial](/docs/content/includes) that holds it. To show braces as text, escape each opening brace (`\{`) or put the text in inline code. For a value every page shares, define a [variable](/docs/content/variables).
22
+
19
23
  ## Files and routes
20
24
 
21
25
  Each file maps to a route by its path under the content root:
@@ -40,7 +44,13 @@ Prefix a file or folder with a number and a `-`, `_`, or `.` to control its orde
40
44
  02-installation.mdx -> /installation
41
45
  ```
42
46
 
43
- A version or an ISO date at the start of a name is part of the name, not an order: `1.2.0.mdx` routes to `/1.2.0`, and `2024-01-05-launch.mdx` to `/2024-01-05-launch`. Only file and folder names lose a prefix (an Obsidian vault's notes count as files). A frontmatter `slug`, and a page from a [content source](/docs/content/sources) such as a CMS or GitHub Releases, keep the name they were given.
47
+ A version or a date at the start of a name is part of the name, not an order: `1.2.0.mdx` routes to `/1.2.0`, and `2024-01-05-launch.mdx` to `/2024-01-05-launch`. Blume reads these as dates:
48
+
49
+ - An ISO date, `YYYY-MM-DD`: `2024-01-05.mdx` routes to `/2024-01-05`.
50
+ - A day or month first, then a four-digit year, `D-M-YYYY` or `M-D-YYYY` with one or two digits for the day and month: `12-05-2022.mdx` routes to `/12-05-2022`.
51
+ - A year and a month, `YYYY-MM`: `2024-01.mdx` routes to `/2024-01`.
52
+
53
+ A date ends the name or is followed by a `-`, `_`, or `.`, like the rest of a name (`2024-01-recap.mdx`). Earlier Blume versions read the first number of the last two forms as an ordering prefix, so `12-05-2022.mdx` published at `/05-2022` and `2024-01.mdx` at `/01`. Those old URLs now redirect to the new ones, unless another page lives there. Only file and folder names lose a prefix (an Obsidian vault's notes count as files). A frontmatter `slug`, and a page from a [content source](/docs/content/sources) such as a CMS or GitHub Releases, keep the name they were given.
44
54
 
45
55
  Ordering has several layers — see [Navigation](/docs/content/navigation) for the full precedence rules.
46
56
 
@@ -28,6 +28,8 @@ The filename is the component name, so it **must be a PascalCase identifier**
28
28
  Islands are for **interactive** UI. For a static component you reuse across pages (a styled callout, a pricing table), use an [MDX override](/docs/configuration/customization) instead — it ships no JavaScript.
29
29
  :::
30
30
 
31
+ An island is the only way to run code in the browser from a page. A JavaScript event handler on a plain HTML element in MDX, like `<button onClick={() => open()}>`, never runs: the page is static HTML, so the function is written into the attribute as text. Blume warns about one as `BLUME_MDX_EVENT_HANDLER` at its line. Move that markup into an island and use the island in the page.
32
+
31
33
  ## Registering islands in `components.ts`
32
34
 
33
35
  If you'd rather keep islands next to the rest of your components — or give them a different name than the file — register them with `defineComponents` as an `mdx` entry with a `client` mode. That's all an island is: an MDX component that hydrates. Each entry is available in every MDX page, and an entry with the same name as an `islands/` file replaces it.
@@ -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: