blume 2.1.0 → 2.1.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/CHANGELOG.md +94 -0
  2. package/dist/cli/{chunk-5cg2rzgf.js → chunk-073rdvgb.js} +19 -48
  3. package/dist/cli/{chunk-5cg2rzgf.js.map → chunk-073rdvgb.js.map} +4 -5
  4. package/dist/cli/{chunk-ga0pf4aj.js → chunk-0n9xkp3d.js} +12 -3
  5. package/dist/cli/{chunk-ga0pf4aj.js.map → chunk-0n9xkp3d.js.map} +3 -3
  6. package/dist/cli/chunk-150jk7km.js +52 -0
  7. package/dist/cli/chunk-150jk7km.js.map +10 -0
  8. package/dist/cli/{chunk-rekqz4qg.js → chunk-313vnn40.js} +16 -15
  9. package/dist/cli/chunk-313vnn40.js.map +10 -0
  10. package/dist/cli/{chunk-gp84za0h.js → chunk-3v3ffbrj.js} +2 -2
  11. package/dist/cli/{chunk-9xr4dgee.js → chunk-3x6x3pph.js} +29 -16
  12. package/dist/cli/chunk-3x6x3pph.js.map +10 -0
  13. package/dist/cli/{chunk-0gz36akr.js → chunk-42t4jxdx.js} +21 -5
  14. package/dist/cli/chunk-42t4jxdx.js.map +12 -0
  15. package/dist/cli/{chunk-ta27feev.js → chunk-54ztw1nj.js} +21 -2
  16. package/dist/cli/chunk-54ztw1nj.js.map +10 -0
  17. package/dist/cli/{chunk-jnwj5kjj.js → chunk-5vjtgs6e.js} +20 -7
  18. package/dist/cli/{chunk-jnwj5kjj.js.map → chunk-5vjtgs6e.js.map} +3 -3
  19. package/dist/cli/chunk-66w9j7p6.js +322 -0
  20. package/dist/cli/chunk-66w9j7p6.js.map +14 -0
  21. package/dist/cli/{chunk-nd1821sk.js → chunk-7ppf1rva.js} +32 -18
  22. package/dist/cli/chunk-7ppf1rva.js.map +10 -0
  23. package/dist/cli/{chunk-p804f3pe.js → chunk-8y2kmjv8.js} +6 -6
  24. package/dist/cli/{chunk-2f7n4qzt.js → chunk-c1y538kd.js} +1 -1
  25. package/dist/cli/{chunk-1n228xqb.js → chunk-c887fj0s.js} +2 -2
  26. package/dist/cli/{chunk-y5h4r2y0.js → chunk-cfyg47zg.js} +15 -5
  27. package/dist/cli/chunk-cfyg47zg.js.map +10 -0
  28. package/dist/cli/{chunk-cmtcnh9f.js → chunk-dzwehpy7.js} +2 -2
  29. package/dist/cli/{chunk-h2a48yhd.js → chunk-e6atmn4h.js} +119 -15
  30. package/dist/cli/{chunk-h2a48yhd.js.map → chunk-e6atmn4h.js.map} +10 -10
  31. package/dist/cli/{chunk-5ffywsmb.js → chunk-e8xw9qkh.js} +8 -6
  32. package/dist/cli/{chunk-5ffywsmb.js.map → chunk-e8xw9qkh.js.map} +3 -3
  33. package/dist/cli/{chunk-ntv4qrt7.js → chunk-e9q7a2ak.js} +14 -14
  34. package/dist/cli/{chunk-xkz5xbtv.js → chunk-ehweg4fr.js} +2366 -1114
  35. package/dist/cli/chunk-ehweg4fr.js.map +218 -0
  36. package/dist/cli/{chunk-zf748avv.js → chunk-ep6nd0rn.js} +176 -281
  37. package/dist/cli/{chunk-zf748avv.js.map → chunk-ep6nd0rn.js.map} +11 -12
  38. package/dist/cli/{chunk-yxmk4pec.js → chunk-fdrkhpjy.js} +16 -15
  39. package/dist/cli/{chunk-yxmk4pec.js.map → chunk-fdrkhpjy.js.map} +3 -3
  40. package/dist/cli/{chunk-5wj2qwn2.js → chunk-ft9vfys6.js} +4 -4
  41. package/dist/cli/{chunk-4h8z9vv5.js → chunk-gb420vvz.js} +89 -46
  42. package/dist/cli/chunk-gb420vvz.js.map +17 -0
  43. package/dist/cli/{chunk-713mngdq.js → chunk-hebn0tpx.js} +76 -10
  44. package/dist/cli/chunk-hebn0tpx.js.map +14 -0
  45. package/dist/cli/{chunk-2n4ps1b3.js → chunk-htrwgabr.js} +40 -22
  46. package/dist/cli/chunk-htrwgabr.js.map +15 -0
  47. package/dist/cli/{chunk-zhvd6adm.js → chunk-hx3xqnk9.js} +13 -13
  48. package/dist/cli/chunk-j134vrp2.js +65 -0
  49. package/dist/cli/chunk-j134vrp2.js.map +10 -0
  50. package/dist/cli/{chunk-y4ykbark.js → chunk-kma9f6sf.js} +2 -2
  51. package/dist/cli/{chunk-py62fewh.js → chunk-mrcccgnh.js} +3 -3
  52. package/dist/cli/{chunk-2f3ndkpw.js → chunk-n0v7m8ry.js} +3 -3
  53. package/dist/cli/{chunk-43zcf4gz.js → chunk-ncj7w596.js} +522 -353
  54. package/dist/cli/chunk-ncj7w596.js.map +35 -0
  55. package/dist/cli/{chunk-9geymqns.js → chunk-nq1d20w2.js} +14 -14
  56. package/dist/cli/{chunk-g2z9x01v.js → chunk-nrns10mj.js} +3 -3
  57. package/dist/cli/{chunk-e7vvk5ya.js → chunk-sds923fa.js} +3 -3
  58. package/dist/cli/{chunk-cy5bm0j0.js → chunk-t2m5j4pr.js} +2 -2
  59. package/dist/cli/{chunk-cy5bm0j0.js.map → chunk-t2m5j4pr.js.map} +1 -1
  60. package/dist/cli/{chunk-yj17vs39.js → chunk-tdqhyxfz.js} +5 -5
  61. package/dist/cli/{chunk-q3wxxq39.js → chunk-tphac74f.js} +5 -5
  62. package/dist/cli/{chunk-6bm0xpgd.js → chunk-w1r4vase.js} +2 -2
  63. package/dist/cli/index.js +24 -19
  64. package/dist/cli/index.js.map +3 -3
  65. package/dist/types/ai/ask-context.d.ts +6 -2
  66. package/dist/types/ai/component-markdown.d.ts +2 -0
  67. package/dist/types/ai/openapi-components.d.ts +4 -1
  68. package/dist/types/astro/integration.d.ts +5 -4
  69. package/dist/types/astro/markdown-negotiation.d.ts +3 -0
  70. package/dist/types/astro/pages.d.ts +10 -0
  71. package/dist/types/cli/init/scaffold.d.ts +1 -1
  72. package/dist/types/components/content/api-field.d.ts +4 -1
  73. package/dist/types/components/content/api-page.d.ts +6 -1
  74. package/dist/types/components/openapi/code-samples.d.ts +29 -0
  75. package/dist/types/components/openapi/helpers.d.ts +22 -1
  76. package/dist/types/components/openapi/security.d.ts +10 -0
  77. package/dist/types/components/openapi/snippets.d.ts +60 -0
  78. package/dist/types/core/changelog-index.d.ts +8 -0
  79. package/dist/types/core/config-input.d.ts +30 -3
  80. package/dist/types/core/data.d.ts +4 -0
  81. package/dist/types/core/i18n-ui.d.ts +2 -0
  82. package/dist/types/core/links.d.ts +8 -0
  83. package/dist/types/core/probe.d.ts +2 -0
  84. package/dist/types/core/project-graph.d.ts +6 -0
  85. package/dist/types/core/redirect-patterns.d.ts +11 -3
  86. package/dist/types/core/schema.d.ts +119 -0
  87. package/dist/types/core/sources/cache.d.ts +3 -0
  88. package/dist/types/core/sources/contentful-rich-text.d.ts +11 -2
  89. package/dist/types/core/sources/contentful.d.ts +17 -1
  90. package/dist/types/core/sources/lexical.d.ts +5 -0
  91. package/dist/types/core/sources/normalize.d.ts +20 -1
  92. package/dist/types/core/sources/remote.d.ts +37 -10
  93. package/dist/types/core/sources/sanity.d.ts +1 -1
  94. package/dist/types/core/types.d.ts +17 -0
  95. package/dist/types/deploy/adapter-output.d.ts +2 -1
  96. package/dist/types/deploy/adapters/cloudflare.d.ts +7 -5
  97. package/dist/types/deploy/adapters/node.d.ts +58 -23
  98. package/dist/types/deploy/adapters/registry.d.ts +10 -0
  99. package/dist/types/deploy/adapters/types.d.ts +3 -2
  100. package/dist/types/deploy/platforms/cloudflare.d.ts +10 -7
  101. package/dist/types/deploy/platforms/node.d.ts +3 -1
  102. package/dist/types/deploy/platforms/types.d.ts +18 -7
  103. package/dist/types/deploy/platforms/vercel.d.ts +20 -1
  104. package/dist/types/deploy/redirects.d.ts +17 -1
  105. package/dist/types/deploy/sitemap.d.ts +2 -1
  106. package/dist/types/deploy/vercel-negotiation.d.ts +42 -21
  107. package/dist/types/openapi/checks.d.ts +27 -0
  108. package/dist/types/openapi/graphql.d.ts +6 -0
  109. package/dist/types/openapi/model.d.ts +15 -4
  110. package/dist/types/openapi/parse.d.ts +3 -0
  111. package/dist/types/openapi/references.d.ts +16 -0
  112. package/dist/types/ratelimit/index.d.ts +1 -1
  113. package/dist/types/ratelimit/schema.d.ts +8 -0
  114. package/dist/types/ratelimit/upstash.d.ts +39 -14
  115. package/dist/types/reference/graphql.d.ts +76 -1
  116. package/dist/types/reference/index.d.ts +1 -1
  117. package/dist/types/reference/openapi.d.ts +2 -2
  118. package/dist/types/reference/options.d.ts +37 -1
  119. package/dist/types/reference/schema.d.ts +84 -0
  120. package/dist/types/search/adapters/registry.d.ts +3 -0
  121. package/dist/types/search/adapters/typesense.d.ts +14 -4
  122. package/dist/types/search/build.d.ts +3 -1
  123. package/dist/types/search/orama-index.d.ts +9 -9
  124. package/dist/types/search/sync/algolia.d.ts +5 -2
  125. package/dist/types/search/sync/index.d.ts +3 -1
  126. package/dist/types/search/sync/typesense.d.ts +11 -7
  127. package/dist/types/sources/contentful.d.ts +16 -0
  128. package/dist/types/sources/github-releases.d.ts +8 -1
  129. package/dist/types/sources/payload.d.ts +2 -0
  130. package/dist/types/sources/registry.d.ts +54 -0
  131. package/dist/types/sources/shared.d.ts +3 -0
  132. package/dist/types/sources/strapi.d.ts +2 -0
  133. package/docs/01-quickstart.mdx +4 -2
  134. package/docs/02-deployment.mdx +31 -13
  135. package/docs/advanced/changelog.mdx +11 -0
  136. package/docs/advanced/skills.mdx +1 -1
  137. package/docs/cli/audit.mdx +30 -7
  138. package/docs/cli/doctor.mdx +5 -1
  139. package/docs/cli/evals.mdx +4 -4
  140. package/docs/cli/translate.mdx +1 -0
  141. package/docs/cli/validate.mdx +4 -3
  142. package/docs/configuration/analytics.mdx +1 -1
  143. package/docs/configuration/assistant.mdx +6 -2
  144. package/docs/configuration/customization.mdx +4 -2
  145. package/docs/configuration/export.mdx +5 -3
  146. package/docs/configuration/index.mdx +3 -2
  147. package/docs/configuration/rate-limiting.mdx +23 -2
  148. package/docs/configuration/search.mdx +37 -14
  149. package/docs/content/components.mdx +2 -2
  150. package/docs/content/frontmatter.mdx +5 -3
  151. package/docs/content/i18n.mdx +2 -2
  152. package/docs/content/includes.mdx +1 -1
  153. package/docs/content/navigation.mdx +4 -4
  154. package/docs/content/sources/contentful.mdx +7 -4
  155. package/docs/content/sources/custom.mdx +7 -3
  156. package/docs/content/sources/github-releases.mdx +10 -2
  157. package/docs/content/sources/index.mdx +3 -3
  158. package/docs/content/sources/notion.mdx +1 -1
  159. package/docs/content/sources/obsidian.mdx +1 -1
  160. package/docs/content/sources/payload.mdx +3 -2
  161. package/docs/content/sources/remote-mdx.mdx +4 -0
  162. package/docs/content/sources/sanity.mdx +2 -2
  163. package/docs/content/sources/strapi.mdx +3 -2
  164. package/docs/content/syntax.mdx +2 -2
  165. package/docs/discoverability/agent-discovery.mdx +1 -1
  166. package/docs/discoverability/index.mdx +1 -1
  167. package/docs/discoverability/json-api.mdx +2 -0
  168. package/docs/discoverability/llms-txt.mdx +1 -1
  169. package/docs/discoverability/markdown.mdx +3 -1
  170. package/docs/discoverability/mcp.mdx +4 -0
  171. package/docs/discoverability/sitemap-and-robots.mdx +1 -1
  172. package/docs/references/api-pages.mdx +1 -1
  173. package/docs/references/asyncapi.mdx +3 -1
  174. package/docs/references/graphql.mdx +13 -1
  175. package/docs/references/openapi.mdx +50 -8
  176. package/package.json +9 -9
  177. package/skills/blume-migrate/SKILL.md +1 -1
  178. package/skills/blume-migrate/references/docusaurus.md +4 -4
  179. package/skills/blume-migrate/references/fumadocs.md +17 -13
  180. package/skills/blume-migrate/references/nextra.md +8 -5
  181. package/skills/blume-migrate/references/starlight.md +9 -7
  182. package/src/ai/ask-context.ts +33 -3
  183. package/src/ai/ask-limits.ts +13 -0
  184. package/src/ai/changelog-markdown.ts +11 -6
  185. package/src/ai/component-markdown.ts +1 -1
  186. package/src/ai/cors.ts +12 -3
  187. package/src/ai/mcp/query.ts +14 -6
  188. package/src/ai/openapi-components.ts +28 -4
  189. package/src/ai/relative-links.ts +4 -38
  190. package/src/astro/generate.ts +78 -48
  191. package/src/astro/integration.ts +16 -13
  192. package/src/astro/markdown-negotiation.ts +3 -0
  193. package/src/astro/module-types.ts +3 -1
  194. package/src/astro/pages.ts +23 -0
  195. package/src/astro/templates.ts +125 -80
  196. package/src/audit/catalog.ts +19 -3
  197. package/src/audit/checks/links.ts +14 -1
  198. package/src/audit/checks/network.ts +110 -6
  199. package/src/audit/checks/redirects.ts +6 -4
  200. package/src/audit/checks/robots.ts +58 -2
  201. package/src/audit/run.ts +3 -0
  202. package/src/audit/types.ts +2 -0
  203. package/src/audit/url.ts +14 -5
  204. package/src/blume-modules.d.ts +5 -1
  205. package/src/cli/args.ts +51 -0
  206. package/src/cli/build-failure.ts +23 -0
  207. package/src/cli/commands/audit.ts +10 -1
  208. package/src/cli/commands/dev.ts +18 -2
  209. package/src/cli/commands/doctor.ts +45 -12
  210. package/src/cli/commands/eval.ts +36 -4
  211. package/src/cli/commands/init.ts +6 -4
  212. package/src/cli/commands/validate.ts +10 -1
  213. package/src/cli/init/scaffold.ts +3 -3
  214. package/src/cli/prepare.ts +6 -3
  215. package/src/cli/required-secrets.ts +1 -1
  216. package/src/components/content/ApiPlayground.astro +6 -2
  217. package/src/components/content/api-field.ts +4 -1
  218. package/src/components/content/api-page.ts +31 -7
  219. package/src/components/content/mermaid-element.ts +15 -1
  220. package/src/components/islands/hooks.ts +27 -1
  221. package/src/components/layout/PageActions.astro +139 -24
  222. package/src/components/layout/RootLayout.astro +10 -2
  223. package/src/components/layout/Search.astro +17 -3
  224. package/src/components/layout/TableOfContents.astro +24 -17
  225. package/src/components/layout/head-scripts.ts +6 -1
  226. package/src/components/layout/search/algolia.ts +10 -1
  227. package/src/components/layout/search/analytics.ts +42 -14
  228. package/src/components/layout/search/endpoint.ts +48 -5
  229. package/src/components/layout/search/pagefind.ts +114 -5
  230. package/src/components/layout/search/types.ts +12 -0
  231. package/src/components/layout/search/typesense.ts +18 -6
  232. package/src/components/openapi/AsyncApiOperation.astro +14 -7
  233. package/src/components/openapi/Authorization.astro +10 -0
  234. package/src/components/openapi/GraphqlOperation.astro +5 -0
  235. package/src/components/openapi/Operation.astro +24 -1
  236. package/src/components/openapi/ParametersTable.astro +8 -4
  237. package/src/components/openapi/Playground.astro +7 -1
  238. package/src/components/openapi/RequestBody.astro +19 -2
  239. package/src/components/openapi/Responses.astro +1 -0
  240. package/src/components/openapi/SchemaTable.astro +15 -3
  241. package/src/components/openapi/async-snippets.ts +61 -5
  242. package/src/components/openapi/async.ts +123 -29
  243. package/src/components/openapi/graphql-helpers.ts +35 -12
  244. package/src/components/openapi/helpers.ts +36 -2
  245. package/src/components/openapi/message-model.ts +54 -11
  246. package/src/components/openapi/message.ts +14 -4
  247. package/src/components/openapi/operation-model.ts +16 -7
  248. package/src/components/openapi/playground-client.ts +24 -5
  249. package/src/components/openapi/playground-schema.ts +1 -1
  250. package/src/components/openapi/schema-tree.ts +44 -14
  251. package/src/components/openapi/security.ts +10 -0
  252. package/src/components/openapi/snippets.ts +5 -3
  253. package/src/components/openapi/validate-json.ts +4 -3
  254. package/src/core/changelog-index.ts +31 -1
  255. package/src/core/component-diagnostics.ts +53 -0
  256. package/src/core/component-overrides.ts +32 -0
  257. package/src/core/config-input.ts +31 -3
  258. package/src/core/data.ts +2 -0
  259. package/src/core/diagnostics.ts +5 -0
  260. package/src/core/i18n-ui.ts +2 -0
  261. package/src/core/includes.ts +1 -1
  262. package/src/core/links.ts +47 -17
  263. package/src/core/probe.ts +26 -6
  264. package/src/core/project-graph.ts +59 -18
  265. package/src/core/redirect-patterns.ts +36 -7
  266. package/src/core/schema.ts +21 -2
  267. package/src/core/sources/cache.ts +6 -0
  268. package/src/core/sources/contentful-rich-text.ts +29 -6
  269. package/src/core/sources/contentful.ts +135 -25
  270. package/src/core/sources/filesystem.ts +26 -4
  271. package/src/core/sources/github-releases.ts +12 -1
  272. package/src/core/sources/lexical.ts +15 -10
  273. package/src/core/sources/mdx-remote.ts +62 -10
  274. package/src/core/sources/normalize.ts +227 -41
  275. package/src/core/sources/notion.ts +7 -1
  276. package/src/core/sources/obsidian.ts +16 -3
  277. package/src/core/sources/payload.ts +38 -15
  278. package/src/core/sources/remote.ts +72 -15
  279. package/src/core/sources/resolve.ts +11 -1
  280. package/src/core/sources/sanity.ts +43 -25
  281. package/src/core/sources/strapi.ts +2 -2
  282. package/src/core/types.ts +18 -0
  283. package/src/core/ui-packs/ar.ts +1 -0
  284. package/src/core/ui-packs/bg.ts +1 -0
  285. package/src/core/ui-packs/bn.ts +1 -0
  286. package/src/core/ui-packs/ca.ts +1 -0
  287. package/src/core/ui-packs/cs.ts +1 -0
  288. package/src/core/ui-packs/da.ts +1 -0
  289. package/src/core/ui-packs/de.ts +1 -0
  290. package/src/core/ui-packs/el.ts +1 -0
  291. package/src/core/ui-packs/es.ts +1 -0
  292. package/src/core/ui-packs/fa.ts +1 -0
  293. package/src/core/ui-packs/fi.ts +1 -0
  294. package/src/core/ui-packs/fr.ts +1 -0
  295. package/src/core/ui-packs/he.ts +1 -0
  296. package/src/core/ui-packs/hi.ts +1 -0
  297. package/src/core/ui-packs/hr.ts +1 -0
  298. package/src/core/ui-packs/hu.ts +1 -0
  299. package/src/core/ui-packs/id.ts +1 -0
  300. package/src/core/ui-packs/it.ts +1 -0
  301. package/src/core/ui-packs/ja.ts +1 -0
  302. package/src/core/ui-packs/ko.ts +1 -0
  303. package/src/core/ui-packs/nl.ts +1 -0
  304. package/src/core/ui-packs/no.ts +1 -0
  305. package/src/core/ui-packs/pl.ts +1 -0
  306. package/src/core/ui-packs/pt-br.ts +1 -0
  307. package/src/core/ui-packs/pt.ts +1 -0
  308. package/src/core/ui-packs/ro.ts +1 -0
  309. package/src/core/ui-packs/ru.ts +1 -0
  310. package/src/core/ui-packs/sk.ts +1 -0
  311. package/src/core/ui-packs/sr.ts +1 -0
  312. package/src/core/ui-packs/sv.ts +1 -0
  313. package/src/core/ui-packs/th.ts +1 -0
  314. package/src/core/ui-packs/tr.ts +1 -0
  315. package/src/core/ui-packs/uk.ts +1 -0
  316. package/src/core/ui-packs/vi.ts +1 -0
  317. package/src/core/ui-packs/zh-tw.ts +1 -0
  318. package/src/core/ui-packs/zh.ts +1 -0
  319. package/src/deploy/adapter-output.ts +7 -2
  320. package/src/deploy/adapters/cloudflare.ts +7 -5
  321. package/src/deploy/adapters/node.ts +47 -9
  322. package/src/deploy/adapters/types.ts +7 -3
  323. package/src/deploy/artifacts.ts +2 -1
  324. package/src/deploy/platforms/cloudflare.ts +13 -9
  325. package/src/deploy/platforms/netlify.ts +2 -1
  326. package/src/deploy/platforms/node.ts +12 -2
  327. package/src/deploy/platforms/types.ts +21 -7
  328. package/src/deploy/platforms/vercel.ts +113 -16
  329. package/src/deploy/redirects.ts +66 -6
  330. package/src/deploy/rss.ts +3 -1
  331. package/src/deploy/sitemap.ts +39 -5
  332. package/src/deploy/vercel-negotiation.ts +290 -102
  333. package/src/eval/agents.ts +18 -1
  334. package/src/eval/prompts.ts +6 -3
  335. package/src/eval/report.ts +34 -13
  336. package/src/eval/run.ts +5 -0
  337. package/src/markdown/relative-links.ts +3 -2
  338. package/src/openapi/checks.ts +273 -0
  339. package/src/openapi/graphql-build.ts +21 -0
  340. package/src/openapi/graphql.ts +11 -0
  341. package/src/openapi/model.ts +15 -3
  342. package/src/openapi/parse.ts +127 -2
  343. package/src/openapi/references.ts +43 -3
  344. package/src/openapi/source.ts +39 -14
  345. package/src/ratelimit/index.ts +1 -1
  346. package/src/ratelimit/runtime.ts +37 -19
  347. package/src/ratelimit/upstash.ts +40 -10
  348. package/src/reference/graphql.ts +15 -1
  349. package/src/reference/index.ts +1 -0
  350. package/src/reference/openapi.ts +2 -2
  351. package/src/reference/options.ts +29 -1
  352. package/src/registry/eject.ts +3 -1
  353. package/src/search/adapters/locale-scope.ts +30 -0
  354. package/src/search/adapters/typesense.ts +14 -4
  355. package/src/search/build.ts +21 -3
  356. package/src/search/orama-index.ts +163 -31
  357. package/src/search/source-pages.ts +29 -0
  358. package/src/search/sync/algolia.ts +99 -9
  359. package/src/search/sync/index.ts +52 -13
  360. package/src/search/sync/typesense.ts +72 -27
  361. package/src/sources/contentful.ts +12 -0
  362. package/src/sources/github-releases.ts +7 -1
  363. package/src/sources/shared.ts +3 -0
  364. package/src/theme/entry.ts +29 -3
  365. package/src/translate/validate.ts +23 -1
  366. package/dist/cli/chunk-0gz36akr.js.map +0 -12
  367. package/dist/cli/chunk-2n4ps1b3.js.map +0 -15
  368. package/dist/cli/chunk-43zcf4gz.js.map +0 -36
  369. package/dist/cli/chunk-4ejxbzee.js +0 -39
  370. package/dist/cli/chunk-4ejxbzee.js.map +0 -10
  371. package/dist/cli/chunk-4h8z9vv5.js.map +0 -16
  372. package/dist/cli/chunk-713mngdq.js.map +0 -14
  373. package/dist/cli/chunk-9xr4dgee.js.map +0 -10
  374. package/dist/cli/chunk-amrb6hgh.js +0 -145
  375. package/dist/cli/chunk-amrb6hgh.js.map +0 -11
  376. package/dist/cli/chunk-nd1821sk.js.map +0 -10
  377. package/dist/cli/chunk-rekqz4qg.js.map +0 -10
  378. package/dist/cli/chunk-ta27feev.js.map +0 -10
  379. package/dist/cli/chunk-xkz5xbtv.js.map +0 -213
  380. package/dist/cli/chunk-y5h4r2y0.js.map +0 -10
  381. /package/dist/cli/{chunk-gp84za0h.js.map → chunk-3v3ffbrj.js.map} +0 -0
  382. /package/dist/cli/{chunk-p804f3pe.js.map → chunk-8y2kmjv8.js.map} +0 -0
  383. /package/dist/cli/{chunk-2f7n4qzt.js.map → chunk-c1y538kd.js.map} +0 -0
  384. /package/dist/cli/{chunk-1n228xqb.js.map → chunk-c887fj0s.js.map} +0 -0
  385. /package/dist/cli/{chunk-cmtcnh9f.js.map → chunk-dzwehpy7.js.map} +0 -0
  386. /package/dist/cli/{chunk-ntv4qrt7.js.map → chunk-e9q7a2ak.js.map} +0 -0
  387. /package/dist/cli/{chunk-5wj2qwn2.js.map → chunk-ft9vfys6.js.map} +0 -0
  388. /package/dist/cli/{chunk-zhvd6adm.js.map → chunk-hx3xqnk9.js.map} +0 -0
  389. /package/dist/cli/{chunk-y4ykbark.js.map → chunk-kma9f6sf.js.map} +0 -0
  390. /package/dist/cli/{chunk-py62fewh.js.map → chunk-mrcccgnh.js.map} +0 -0
  391. /package/dist/cli/{chunk-2f3ndkpw.js.map → chunk-n0v7m8ry.js.map} +0 -0
  392. /package/dist/cli/{chunk-9geymqns.js.map → chunk-nq1d20w2.js.map} +0 -0
  393. /package/dist/cli/{chunk-g2z9x01v.js.map → chunk-nrns10mj.js.map} +0 -0
  394. /package/dist/cli/{chunk-e7vvk5ya.js.map → chunk-sds923fa.js.map} +0 -0
  395. /package/dist/cli/{chunk-yj17vs39.js.map → chunk-tdqhyxfz.js.map} +0 -0
  396. /package/dist/cli/{chunk-q3wxxq39.js.map → chunk-tphac74f.js.map} +0 -0
  397. /package/dist/cli/{chunk-6bm0xpgd.js.map → chunk-w1r4vase.js.map} +0 -0
@@ -58,7 +58,7 @@ ai:
58
58
  ---
59
59
  ```
60
60
 
61
- The page still renders, stays in search, and keeps its place in the sitemap — only the `llms.txt` files skip it.
61
+ The page still renders, stays in search, and keeps its place in the sitemap — only the `llms.txt` files and the page map in your [site's skill](/docs/discoverability/agent-discovery#your-sites-skill) skip it.
62
62
 
63
63
  ## Bringing your own
64
64
 
@@ -17,7 +17,7 @@ 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. 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, `<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.
21
21
 
22
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.
23
23
 
@@ -25,6 +25,8 @@ When you want the MDX itself, use the `.mdx` variant: its components stay as wri
25
25
 
26
26
  Agents don't need to know the `.md` convention: requesting a page's own URL with an [`Accept: text/markdown`](https://acceptmarkdown.com) header serves the Markdown variant at the same address, with `Vary: Accept` so caches keep the two apart. The dev server honors the header out of the box, and a [Vercel or Cloudflare server build](/docs/deployment#server-rendering) wires the same negotiation into the deploy automatically — routing rules on Vercel, a generated Worker on Cloudflare — no configuration needed. The homepage always negotiates, even when it's a custom landing page rather than a content page: its Markdown mirror falls back to the [`llms.txt`](/docs/discoverability/llms-txt) index, so an agent asking the site root for Markdown gets the machine-readable map of the site. Markdown responses also carry an `x-markdown-tokens` header — an estimated token count (~4 characters per token), following the convention of [Cloudflare's Markdown for Agents](https://developers.cloudflare.com/fundamentals/reference/markdown-for-agents/) — on every surface where Blume controls response headers: the dev server, server-rendered responses, and the negotiated homepage on Vercel and Cloudflare. Other deploy targets serve prerendered pages from a static layer with no request-time hook, so agents there fetch the `.md` URL directly; the [agent readability manifest](/docs/discoverability/agent-discovery#agent-readability) advertises `contentNegotiation` only on deployments that honor the header.
27
27
 
28
+ A client gets Markdown when its `Accept` header lists `text/markdown` (or `text/x-markdown`, in any case) with a q-value above zero and at least as high as `text/html`'s: `text/markdown` and `text/markdown, text/html` get Markdown, `text/html, text/markdown;q=0.9` gets HTML. Vercel's routing rules can't compare two q-values, so when both types carry one below 1 (`text/markdown;q=0.9, text/html;q=0.8`), Vercel serves HTML where the dev server and Cloudflare serve Markdown. Send `text/markdown` without a q-value to get Markdown everywhere.
29
+
28
30
  Missing pages negotiate too. The default [404 page](/docs/advanced/custom-pages#404-page) has a Markdown twin at `/404.md` — the not-found message followed by recovery links to every top-level section, the sitemap, and `llms.txt` — and on a Vercel or Cloudflare server build a request for a nonexistent URL that prefers Markdown gets that body with a real `404` status rather than the HTML shell. On Vercel the same goes for any `.md` URL with no page behind it. A 404 page of your own (a `pages/404.astro`, or a content page at `/404`) replaces the default along with its `/404.md` twin, so missing pages then answer with your HTML page.
29
31
 
30
32
  ### Custom component serializers
@@ -29,6 +29,8 @@ The server exposes read-only tools — `search_docs`, `get_page`, `list_pages`,
29
29
 
30
30
  `search_docs` runs its own full-text index, so it works regardless of your [search](/docs/configuration/search) provider — and even with `search: false`. The MCP server is a separate feature from on-page search.
31
31
 
32
+ [Hidden pages](/docs/content/navigation#hidden-pages) stay out of `list_pages` and `resources/list`, and out of `search_docs` unless [`search.indexing.includeHiddenPages`](/docs/configuration/search#excluding-pages) is on, as they stay out of site search and `llms.txt`. `get_page` and `resources/read` still return a hidden page by its route: hidden isn't private, and the page is still served at its URL and as Markdown.
33
+
32
34
  The same tools are available over plain HTTP as the [JSON API](/docs/discoverability/json-api), for frameworks that don't speak MCP.
33
35
 
34
36
  The endpoint reads a request body only up to 64 KB and answers anything larger with `413`. A call to a tool that doesn't exist, or with `arguments` that aren't an object, gets the JSON-RPC Invalid params error (`-32602`).
@@ -49,6 +51,8 @@ Both tools also accept a `filters` object matching against the facets a site dec
49
51
 
50
52
  Every `filters` entry must match (results carry their facet values, and `list_pages` shows each page's), so a knowledge base can drive progressive-disclosure agent workflows — enumerate the enforced standards, search only within them — without any server of its own.
51
53
 
54
+ A value matches exactly, case included, as `list_pages` shows it: `"enforced"` doesn't match a page whose `status` is `Enforced`. Facet values are strings, so a number or boolean in `filters` matches its string form (`{"priority": 1}` finds `priority: 1`), and a value of any other shape, such as a list, is ignored rather than filtering.
55
+
52
56
  ## Server output required
53
57
 
54
58
  The MCP server is a live endpoint (`/mcp`), so it can't run on a static build. Name a host adapter from `blume/deploy` to switch to server output:
@@ -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. 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, 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: {
@@ -42,7 +42,7 @@ Creates a user and sends them an invite.
42
42
 
43
43
  ## The playground and samples
44
44
 
45
- The page's `ParamField`s become the Try it panel's inputs, the way an operation's parameters do in a spec: `path`, `query`, and `header` fields are parameters, and `body` fields make up a JSON body, with a field's nested fields (inside its [`Expandable`](/docs/content/components#expandable)) as the properties of an object. A `type` of `string[]` is an array, and one that isn't a JSON type, such as `enum<string>`, is sent as a string. A field's `default` fills the body, and its `placeholder` is the example value the samples show.
45
+ The page's `ParamField`s become the Try it panel's inputs, the way an operation's parameters do in a spec: `path`, `query`, and `header` fields are parameters, and `body` fields make up a JSON body, with a field's nested fields (inside its [`Expandable`](/docs/content/components#expandable)) as the properties of an object. A `type` of `string[]` is an array, and one that isn't a JSON type, such as `enum<string>`, is sent as a string. A field's `default` fills the body, and its `placeholder` is the example value the samples show. An optional body field with neither starts empty and stays out of the request until the reader fills it in.
46
46
 
47
47
  Beside the panel, the page gets request samples in cURL, JavaScript, and Python. A page with its own `RequestExample` shows that in their place.
48
48
 
@@ -3,7 +3,7 @@ title: AsyncAPI
3
3
  description: Drop in an AsyncAPI spec and get a native event reference — one real page per send and receive operation, in your sidebar and search.
4
4
  ---
5
5
 
6
- Event-driven APIs use the `asyncapi()` adapter from `blume/reference`, listed under `reference` beside any [OpenAPI](/docs/references/openapi) or [GraphQL](/docs/references/graphql) adapters. It takes the same options as `openapi()` and renders with the same native renderer. Each `send`/`receive` operation becomes a real page with message payload and header schema tables, channel parameters, protocol bindings, an Authorization section derived from the spec's `securitySchemes` (server-level and operation-level, alternatives as "or" groups), and a [Try it](#try-it-for-events) message composer. Because each operation is a genuine Blume page, it gets its own URL, shows up in **site search** and `llms.txt`, and gets an Open Graph image — the same as any hand-written doc.
6
+ Event-driven APIs use the `asyncapi()` adapter from `blume/reference`, listed under `reference` beside any [OpenAPI](/docs/references/openapi) or [GraphQL](/docs/references/graphql) adapters. It takes the same options as `openapi()` and renders with the same native renderer. Each `send`/`receive` operation becomes a real page with message payload and header schema tables, channel parameters, protocol bindings, an Authorization section derived from the spec's `securitySchemes` (server-level and operation-level, alternatives as "or" groups, and when a channel's servers differ, which servers each scheme applies to), and a [Try it](#try-it-for-events) message composer. Because each operation is a genuine Blume page, it gets its own URL, shows up in **site search** and `llms.txt`, and gets an Open Graph image — the same as any hand-written doc.
7
7
 
8
8
  ```ts blume.config.ts lineNumbers
9
9
  import { defineConfig } from "blume";
@@ -30,6 +30,8 @@ AsyncAPI **2.x specs are normalized to 3.x automatically** with the official Asy
30
30
 
31
31
  Code samples are **protocol-aware**, keyed off the operation's binding (or its servers' protocol): `wscat` and a browser `WebSocket` snippet for WebSockets, `kcat` for Kafka, `mosquitto_pub`/`mosquitto_sub` for MQTT. `codeSamples` filters that set, the same way it picks languages on `openapi()`, and `codeSamples: false` shows none. A protocol without a supported tool renders the message payload example alone rather than a fabricated client.
32
32
 
33
+ The `kcat` sample reads the Kafka bindings too. A channel binding's `topic` replaces the channel address as the topic name. A message binding's `key` keys the message: the sample produces `key|payload` lines with `-K '|'`, and consumes with the same flag so each key prints before its message. The key value comes from the key schema like a payload's does, so give the schema an `examples` entry. When the server declares a SASL scheme (`plain`, `scramSha256`, `scramSha512`, or `gssapi`), the sample adds the `-X` settings librdkafka needs, reading the username and password from `$KAFKA_USERNAME` and `$KAFKA_PASSWORD`. A `kafka-secure` server adds TLS.
34
+
33
35
  ## Shared options
34
36
 
35
37
  Everything documented for [OpenAPI](/docs/references/openapi) carries over, [`playground`](#try-it-for-events) included: [`route`](/docs/references/openapi#route), [`sources`](/docs/references/openapi#multiple-specs) with `label`/`route`, `expandSchemas`, the [per-source indexing](/docs/references/openapi#per-source-indexing) flags (`seoDescriptionSuffix` too — the generated sentence names the channel and action instead of an endpoint), and search indexing by operation summary, description, tag, and endpoint (`SEND user/signup`).
@@ -40,7 +40,7 @@ navigation: {
40
40
 
41
41
  ## Generated examples
42
42
 
43
- Every operation page carries a complete, valid example operation — one variable per argument, typed off the schema, with a bounded-depth selection set over the return type — plus matching example variables and an example response that mirrors the same selection. Code samples show the exact HTTP request (a JSON `POST` of `{ query, variables }`) in each configured language. The same [languages](/docs/references/openapi#code-samples-and-schemas) as `openapi()` are available, and `codeSamples: false` shows none:
43
+ Every operation page carries a complete, valid example operation — one variable per argument, typed off the schema, with a bounded-depth selection set over the return type — plus matching example variables and an example response that mirrors the same selection. A variable whose argument declares a default starts at that default, and the selection leaves deprecated fields out. Code samples show the exact HTTP request (a JSON `POST` of `{ query, variables }`) in each configured language. The same [languages](/docs/references/openapi#code-samples-and-schemas) as `openapi()` are available, and `codeSamples: false` shows none:
44
44
 
45
45
  ```ts blume.config.ts lineNumbers
46
46
  reference: [
@@ -82,6 +82,18 @@ reference: [
82
82
 
83
83
  Query and mutation pages render an interactive panel: edit the request body (the query and variables), point it at your endpoint or a custom URL, and send — the code samples update live so what you copy is byte-for-byte what was sent. Disable it with `playground: false`. Subscription pages show the generated operation and an example event instead: subscriptions run over a stateful transport (WebSocket or SSE) that the playground's single HTTP `POST` can't speak.
84
84
 
85
+ A schema doesn't say how its API authenticates, so the panel sends no credentials until `auth` does. It takes the same shape as [`api.auth`](/docs/references/api-pages#site-defaults) for hand-written endpoint pages: `method` is `bearer`, `basic`, `key` (an API key in a header), or `none`, and `name` is the header an API key goes in (`x-api-key` by default). Every operation page then gets an **Authorization** section, the panel a credential field that works like the [OpenAPI one](/docs/references/openapi#credentials), and the code samples a placeholder credential. A per-source `auth` overrides the adapter's:
86
+
87
+ ```ts blume.config.ts lineNumbers
88
+ reference: [
89
+ graphql({
90
+ spec: "./schema.graphql",
91
+ endpoint: "https://api.example.com/graphql",
92
+ auth: { method: "bearer" },
93
+ }),
94
+ ],
95
+ ```
96
+
85
97
  If your GraphQL API doesn't allow cross-origin requests from the docs site, route sends through a CORS proxy — a URL of your own, or `true` for the built-in `/_api-proxy` endpoint (under your `basePath`, if you set one; it needs [server output](/docs/deployment#server-rendering): a host adapter such as `deployment: vercel()` from `blume/deploy`). The built-in proxy only forwards to origins your documented specs declare — each configured GraphQL `endpoint`, plus any absolute `servers[].url` from a documented [OpenAPI spec](/docs/references/openapi) — so a public docs deployment can't be aimed at other hosts. That makes `endpoint` required for a working proxy: without one, the proxy has no origin to allow for this reference and refuses every send (the build warns about this). The same body limit, header forwarding, and response headers apply as for the [OpenAPI proxy](/docs/references/openapi#try-it-playground).
86
98
 
87
99
  ```ts blume.config.ts lineNumbers
@@ -18,7 +18,7 @@ export default defineConfig({
18
18
  });
19
19
  ```
20
20
 
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. 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).
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
23
  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:
24
24
 
@@ -28,18 +28,30 @@ navigation: {
28
28
  }
29
29
  ```
30
30
 
31
+ 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.
32
+
31
33
  :::note
32
- Operations are indexed for search by their **summary, description, tag, and endpoint** (`GET /pets/{id}`). The rendered schema tables and code samples aren't full-text indexed; search matches an operation's title, prose, section, and path, then links to its own page.
34
+ 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).
33
35
  :::
34
36
 
35
37
  ## A local spec
36
38
 
37
- A relative path is resolved from your project root and read at build time. Both JSON and YAML work:
39
+ A relative path is resolved from your project root and read at build time. Both JSON and YAML work, and `blume dev` watches the file, along with any local [overlays](#overlays), so the reference updates when you save the spec:
38
40
 
39
41
  ```ts blume.config.ts lineNumbers
40
42
  reference: [openapi({ spec: "./openapi.yaml" })],
41
43
  ```
42
44
 
45
+ ## OpenAPI 3.2
46
+
47
+ OpenAPI 3.2 specs render as written. A `query` operation, for the HTTP `QUERY` method (a safe request that carries a body), gets a page, Try it, and code samples like any other method. Fields 3.2 adds that Blume doesn't show, such as a tag's or a response's `summary`, are ignored rather than rejected. Some 3.2 additions don't render yet:
48
+
49
+ - `additionalOperations`, for methods a path item doesn't name directly (like `COPY`): those operations are left out, with a `BLUME_OPENAPI_UNSUPPORTED` [warning](#spec-warnings).
50
+ - `in: querystring` parameters: they get no row in the parameter table, no Try it input, and no place in the code samples, with the same warning.
51
+ - `itemSchema`, the schema of each item in a streamed body (`text/event-stream`, `application/jsonl`): the page shows no schema for that body unless `schema` is set beside it.
52
+ - Examples written with `dataValue` or `serializedValue` in place of `value`: the page doesn't show them.
53
+ - Nested tags (`parent`) and tag `kind`: tags stay one flat list in the sidebar and the overview.
54
+
43
55
  ## Route
44
56
 
45
57
  `route` controls where the reference mounts — the overview page and the prefix for every operation route (and the route you point a navigation tab at):
@@ -55,7 +67,7 @@ reference: [
55
67
 
56
68
  ## Code samples and schemas
57
69
 
58
- `codeSamples` picks which languages render per operation, in the order you list them. It defaults to `["curl", "js", "python"]`. `expandSchemas` starts nested schema rows expanded rather than collapsed:
70
+ `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:
59
71
 
60
72
  ```ts blume.config.ts lineNumbers
61
73
  reference: [
@@ -92,6 +104,8 @@ Every sample is plain, copy-pasteable code for the request, written with the lan
92
104
 
93
105
  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.
94
106
 
107
+ 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.
108
+
95
109
  ### Your own samples
96
110
 
97
111
  To show your SDK instead of a raw HTTP call, add `x-codeSamples` to an operation in the spec. It's the extension Redocly defined, and Speakeasy and Stainless can write it for you. Each entry takes a `lang`, the `source`, and an optional `label`:
@@ -111,7 +125,19 @@ paths:
111
125
  planter list
112
126
  ```
113
127
 
114
- 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`.
128
+ 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.
129
+
130
+ 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:
131
+
132
+ ```yaml openapi.yaml lineNumbers
133
+ paths:
134
+ /plants:
135
+ get:
136
+ x-codeSamples:
137
+ - lang: go
138
+ source:
139
+ $ref: ./code_samples/go/list_plants.go
140
+ ```
115
141
 
116
142
  ## Try it playground
117
143
 
@@ -150,7 +176,7 @@ The built-in proxy only forwards requests to the origins your specs declare in `
150
176
 
151
177
  ## Multiple specs
152
178
 
153
- Use `sources` to publish more than one spec from one adapter. Each source gets its own overview route and operation pages, and shares the adapter's display options. Give each a `label` (used for the sidebar and to derive its route), or set an explicit `route`:
179
+ Use `sources` to publish more than one spec from one adapter. Each source gets its own overview route and operation pages, and shares the adapter's display options. Give each a `label`, which names its group in the sidebar as written and derives its route, or set an explicit `route`:
154
180
 
155
181
  ```ts blume.config.ts lineNumbers
156
182
  reference: [
@@ -246,7 +272,7 @@ The OpenAPI semantics carry over as written:
246
272
 
247
273
  ## Webhooks and callbacks
248
274
 
249
- Your API's outgoing requests get documented too. Each [webhook](https://spec.openapis.org/oas/v3.1.0#oas-webhooks) in the spec's top-level `webhooks` gets its own page, like an operation: filed under its first tag, or a **Webhooks** group when it has none. The page shows the payload's schema and the responses your endpoint should send back. In place of the Try it panel and code samples, the rail shows an example payload, since your API sends this request rather than serving it. A webhook page lists only the `security` the webhook declares itself: the spec's root `security` guards calls to your API, so it doesn't apply to requests the API sends.
275
+ Your API's outgoing requests get documented too. Each [webhook](https://spec.openapis.org/oas/v3.1.0#oas-webhooks) in the spec's top-level `webhooks` gets its own page, like an operation: filed under its first tag, or a **Webhooks** group when it has none. The page shows the payload's schema and the responses your endpoint should send back. In place of the Try it panel and code samples, the rail shows an example payload, since your API sends this request rather than serving it. A webhook page lists only the `security` the webhook declares itself: the spec's root `security` guards calls to your API, so it doesn't apply to requests the API sends. A webhook that's a `$ref` to a shared path item is left out, with a [`BLUME_OPENAPI_REF_PATH_ITEM`](#spec-warnings) warning.
250
276
 
251
277
  ```yaml openapi.yaml lineNumbers
252
278
  webhooks:
@@ -263,10 +289,26 @@ webhooks:
263
289
  description: Return a 200 to acknowledge the event.
264
290
  ```
265
291
 
266
- An operation's [`callbacks`](https://spec.openapis.org/oas/v3.1.0#callback-object), the requests your API makes back to a URL the caller supplied, render in a **Callbacks** section on that operation's page. Each shows the URL expression (`{$request.body#/callbackUrl}`), its method, its request body, and its expected responses. Callbacks declared under `components.callbacks` and referenced with `$ref` work the same way.
292
+ A 3.0 spec's webhooks under the `x-webhooks` extension become `webhooks` when Blume upgrades the spec to 3.1. A spec already at 3.1 or later isn't upgraded, so webhooks it still keeps under `x-webhooks` render nothing, with a [`BLUME_OPENAPI_X_WEBHOOKS`](#spec-warnings) warning: rename the field to `webhooks`.
293
+
294
+ An operation's [`callbacks`](https://spec.openapis.org/oas/v3.1.0#callback-object), the requests your API makes back to a URL the caller supplied, render in a **Callbacks** section on that operation's page. Each shows the URL expression (`{$request.body#/callbackUrl}`), its method, its parameters (a signature header, say), its request body, and its expected responses. Callbacks declared under `components.callbacks` and referenced with `$ref` work the same way.
267
295
 
268
296
  Webhook pages are real pages: they're in search, `llms.txt`, and the MCP server, and their Markdown tells agents it's a request the API sends, so an agent won't try to call it.
269
297
 
298
+ ## Spec warnings
299
+
300
+ Blume doesn't validate a spec against the OpenAPI schema: run a linter such as [Spectral](https://github.com/stoplightio/spectral) or [oasdiff](https://github.com/oasdiff/oasdiff) in CI for that. It does warn, in `blume dev`, `blume build`, and `blume check`, about mistakes that would otherwise render wrong without any sign, and about parts of a spec it leaves out. Each warning names the spec, and the rest of the reference still builds:
301
+
302
+ | Code | What's wrong |
303
+ | --- | --- |
304
+ | `BLUME_OPENAPI_PATH_PARAMETER_MISSING` | A `{name}` in an operation's path has no `in: path` parameter of that name, so the page has no input for it, and Try it and the code samples send the placeholder as literal text. |
305
+ | `BLUME_OPENAPI_PATH_PARAMETER_UNUSED` | An `in: path` parameter has no `{name}` in the path, so the value Try it asks for never reaches the URL. |
306
+ | `BLUME_OPENAPI_UNKNOWN_SECURITY_SCHEME` | A `security` requirement names a scheme that `components.securitySchemes` doesn't define, so the [Authorization](#authorization) section can't describe it and requests send no credential for it. |
307
+ | `BLUME_OPENAPI_UNSUPPORTED` | The spec uses an [OpenAPI 3.2](#openapi-32) feature Blume doesn't render yet: `additionalOperations`, which leaves those operations out, or an `in: querystring` parameter, which leaves the query string out of the page, Try it, and the code samples. |
308
+ | `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. |
309
+ | `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`. |
310
+ | `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. |
311
+
270
312
  ## Embedding Scalar instead
271
313
 
272
314
  `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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "blume",
3
- "version": "2.1.0",
3
+ "version": "2.1.1",
4
4
  "description": "The open-source docs framework for humans and agents.",
5
5
  "keywords": [
6
6
  "agents",
@@ -115,16 +115,16 @@
115
115
  "@astrojs/markdown-satteri": "^0.4.1",
116
116
  "@astrojs/mdx": "^8.0.1",
117
117
  "@astrojs/node": "^11.1.5",
118
- "@astrojs/react": "^6.0.5",
119
- "@astrojs/vercel": "^11.0.10",
118
+ "@astrojs/react": "^7.0.0",
119
+ "@astrojs/vercel": "^11.0.11",
120
120
  "@clack/prompts": "^1.8.0",
121
121
  "@iconify-json/lucide": "^1.2.131",
122
122
  "@iconify/types": "^2.0.0",
123
123
  "@iconify/utils": "^3.1.7",
124
124
  "@modelcontextprotocol/sdk": "^1.30.0",
125
125
  "@orama/orama": "^3.1.18",
126
- "@pierre/diffs": "^1.4.2",
127
- "@scalar/astro": "^0.4.21",
126
+ "@pierre/diffs": "^1.5.1",
127
+ "@scalar/astro": "^0.4.26",
128
128
  "@scalar/openapi-parser": "^0.29.1",
129
129
  "@scalar/openapi-types": "^0.9.5",
130
130
  "@shikijs/transformers": "^4.4.3",
@@ -136,12 +136,11 @@
136
136
  "@vercel/analytics": "^2.0.1",
137
137
  "ai": "^7.0.99",
138
138
  "astro": "^7.3.2",
139
- "babel-plugin-react-compiler": "^1.0.0",
140
139
  "chokidar": "^5.0.0",
141
140
  "citty": "^0.2.2",
142
141
  "consola": "^3.4.2",
143
142
  "cross-spawn": "^7.0.6",
144
- "dompurify": "^3.4.15",
143
+ "dompurify": "^3.4.16",
145
144
  "dotenv": "^17.4.2",
146
145
  "epub-gen-memory": "^1.1.2",
147
146
  "es-module-lexer": "^3.0.2",
@@ -151,7 +150,7 @@
151
150
  "gray-matter": "^4.0.3",
152
151
  "html-escaper": "^3.0.3",
153
152
  "jiti": "^2.7.0",
154
- "js-yaml": "^5.4.1",
153
+ "js-yaml": "^5.4.2",
155
154
  "jsonpath-rfc9535": "^1.3.0",
156
155
  "katex": "^0.18.7",
157
156
  "markdown-table": "^3.0.4",
@@ -165,6 +164,7 @@
165
164
  "nanotar": "^0.3.0",
166
165
  "node-html-parser": "^9.0.4",
167
166
  "openapi-sampler": "^1.7.5",
167
+ "oxc-transform-react": "^0.145.0",
168
168
  "p-limit": "^7.3.2",
169
169
  "p-map": "^7.0.7",
170
170
  "p-retry": "^8.0.1",
@@ -194,7 +194,7 @@
194
194
  "zod": "^4.6.2"
195
195
  },
196
196
  "devDependencies": {
197
- "@ai-sdk/anthropic": "^4.0.65",
197
+ "@ai-sdk/anthropic": "^4.0.68",
198
198
  "@ai-sdk/google": "^4.0.82",
199
199
  "@ai-sdk/openai": "^4.0.78",
200
200
  "@ai-sdk/openai-compatible": "^3.0.48",
@@ -116,7 +116,7 @@ Also valid: `date`/`authors` (blog/changelog feeds), `changelog` (changelog meta
116
116
  - **Callouts as directives:** `:::note`, `:::tip`, `:::warning`, `:::danger`, `:::info`, `:::success`, with an optional title in brackets: `:::warning[Heads up]`. Aliases `caution`→warning, `error`→danger, `important`→note, `warn`→warning.
117
117
  - **No-import MDX components:** `Callout`, `Card`/`CardGroup`, `Columns`/`Column`, `Steps`/`Step`, `Tabs`/`Tab`, `Accordion`/`AccordionItem`, `Expandable`, `FileTree`, `Tree`/`Tree.Folder`/`Tree.File`, `CodeGroup`, `Frame`, `Panel`, `Tooltip`, `Tile`, `Badge`, `Icon`, `TypeTable`/`AutoTypeTable`, `Color`, `YouTube`, `Visibility`, `GithubInfo`, `Component`, `CodeBlock`, `Diff`, `Prompt`, `Math`. (**Not** shipped — convert away: `<Warning>` → the `:::warning` directive, and the `ParamField`/`ResponseField`/`RequestField` field family → `TypeTable` rows or the OpenAPI reference. See the reference files for targets.)
118
118
  - **Fenced-code superpowers:** ` ```package-install ` → package-manager tabs; ` ```mermaid ` → a rendered diagram; code-block titles (` ```ts server.ts `), line numbers (`lineNumbers`), and highlighting (`{1,4-5}`, `// [!code ++]`).
119
- - **Math:** block math `$$…$$` renders in `.mdx` with **no config** (there is no `markdown.math` field). Inline `$…$` is **not** supported — a bare `$` stays literal text; convert inline math to display math or drop it (report).
119
+ - **Math:** `$$…$$` renders in `.mdx` with **no config** (there is no `markdown.math` field) — as a block on lines of its own, or inline within a sentence. A single `$` always stays literal text, so convert each inline `$…$` to `$$…$$` in place, inside its sentence (not onto a line of its own), checking every pair is math and not currency or a shell variable.
120
120
 
121
121
  ### OpenAPI
122
122
 
@@ -34,7 +34,7 @@ Read `themeConfig`, `presets`, and `plugins`:
34
34
  | `@docusaurus/theme-mermaid` | delete the dep — ` ```mermaid ` renders natively (in `.mdx`) |
35
35
  | OpenAPI doc plugins (`docusaurus-plugin-openapi-docs`, `redocusaurus`) | delete the plugin **and its generated pages** — add `openapi({ spec })` (from `blume/reference`) to the top-level `reference` list; `redocusaurus` `specs[].route` → the adapter's `route`, one `openapi()` entry (or source) per spec (see SKILL.md "OpenAPI") |
36
36
  | GraphQL doc generators (`@graphql-markdown/docusaurus`, `@edno/docusaurus2-graphql-doc-generator`) | delete the plugin **and its generated pages** — add `graphql({ spec, endpoint })` (from `blume/reference`) to the top-level `reference` list instead (see SKILL.md "GraphQL") |
37
- | `remark-math` + `rehype-katex` | delete — block `$$…$$` renders in `.mdx` with no config (no `markdown.math` field exists); **inline `$…$` is not supported** — convert or drop (report) |
37
+ | `remark-math` + `rehype-katex` | delete — `$$…$$` renders in `.mdx` with no config (no `markdown.math` field exists), as a block or inline within a sentence; a single `$` stays literal, so convert each inline `$…$` to `$$…$$` inside its sentence |
38
38
  | Multi-instance docs plugins (`plugin-content-docs` with `id`) | one folder (and usually one `navigation.tabs` entry) per instance |
39
39
 
40
40
  ## Static assets — required move
@@ -45,7 +45,7 @@ Every Docusaurus repo serves **`static/`** at the site root (`static/img/foo.png
45
45
 
46
46
  - **Autogenerated sidebar** (`{ type: 'autogenerated', dirName: '...' }`) → Blume's default filesystem navigation. Docusaurus strips numeric prefixes (`01-`) exactly like Blume, so the convention round-trips; no config needed.
47
47
  - **Explicit sidebar** (arrays of doc IDs, categories, links) → restructure into folders where possible; use `navigation.sidebar` only for shapes files can't express. A category `{ type: 'category', label, items }` → a folder (label → `meta.ts` `title`); `collapsed` → `meta.ts` `collapsed`, and collapsible rendering → that folder's `meta.ts` `display: "group"` (or `navigation.sidebar.display: "group"` once in config when every category collapses).
48
- - **`_category_.json`** (also `.yml`) (`{ label, position, collapsed, collapsible, link, className, customProps }`) → a folder `meta.ts`: `label`→`title`, `position`→`order`, `collapsed`→`collapsed`. `link.type: 'generated-index'` → an `index` page in the folder — **and the old URL was `/docs/category/<slug>`**, so add a redirect and rewrite inbound links. `link.type: 'doc'` → make that doc the folder's `index`. `collapsible: false` → that folder's `meta.ts` `display: "flat"`, so it stays a plain heading even under a global `"group"` mode. `className`/`customProps` → drop (report).
48
+ - **`_category_.json`** (also `.yml`) (`{ label, position, collapsed, collapsible, link, className, customProps }`) → a folder `meta.ts`: `label`→`title`, `position`→`order`, `collapsed`→`collapsed`. `link.type: 'generated-index'` → an `index` page in the folder (its `title`/`description` from `link.title`/`link.description`) plus `directory: "card"` in the folder's `meta.ts`, which lists the folder's pages below it as the generated index did — **and the old URL was `/docs/category/<slug>`**, so add a redirect and rewrite inbound links. `link.type: 'doc'` → make that doc the folder's `index`. `collapsible: false` → that folder's `meta.ts` `display: "flat"`, so it stays a plain heading even under a global `"group"` mode. `className`/`customProps` → drop (report).
49
49
  - **`src/pages/` — inventory, don't nuke.** Nearly every repo has a React landing page (`src/pages/index.tsx`) and often extra Markdown pages. Markdown pages → content pages; the React home page → rebuild as a docs index or a custom `.astro` page under `content.pages` — report either way.
50
50
 
51
51
  ## Versioned docs
@@ -61,7 +61,7 @@ A Docusaurus `blog/` → Blume `type: blog` pages. **Dates come from filenames/f
61
61
  - **`.md` vs `.mdx` — both majors need renames, for opposite reasons.** Blume parses `.md` as plain Markdown: no directives, no JSX, no `$$` math, no mermaid/package-install fences. **v3** treats `.md` as MDX (so a `.md` with imports/JSX/`{}` renders them as literal text in Blume); **v2** content is looser MDX v1. Rule: **rename any `.md` that contains admonitions, JSX, imports, or math to `.mdx`** — for typical Docusaurus repos that is most files.
62
62
  - **Admonitions are directives — but check the version.** v3: `:::note`, `:::tip`, `:::info`, `:::warning`, `:::danger` pass through; `:::caution` → `:::warning` (or rely on Blume's alias); titles `:::note[Title]` work. **v2:** titles are space-separated (`:::note Your Title`) — rewrite to brackets or the title is silently lost; and v2's `:::warning` rendered **red/danger** — audit whether it should become `:::danger`.
63
63
  - **Tabs:** `<Tabs>`/`<TabItem label="…" value="…">` → `<Tabs>`/`<Tab title="…">`. Drop `groupId`/`queryString`/`value`; strip the `@theme/Tabs` imports.
64
- - **Theme JSX in content:** `<DocCardList/>` (standard on category index pages) → a `CardGroup` of `Card` links to the folder's pages, one per child (nothing in Blume lists a folder's children on its index page, so deleting it leaves the page empty); `<TOCInline/>` → drop (report); `<CodeBlock>` JSX → a fenced code block; `<Admonition>` → the matching directive; `<details>`/`<summary>` → `<Accordion>`/`<AccordionItem>` or leave as raw HTML.
64
+ - **Theme JSX in content:** `<DocCardList/>` (standard on category index pages) → delete it and set `directory: "card"` in that folder's `meta.ts`: Blume then lists the folder's pages as cards below the `index` page's content and keeps the list current (nested folders inherit the setting; give one `directory: "none"` to opt out). A hand-written `CardGroup` is only for a curated subset; `<TOCInline/>` → drop (report); `<CodeBlock>` JSX → a fenced code block; `<Admonition>` → the matching directive; `<details>`/`<summary>` → `<Accordion>`/`<AccordionItem>` or leave as raw HTML.
65
65
  - **`@theme/*` / `@site/*` imports** — strip `@theme/*` (Blume injects components globally); rewrite `@site/` asset/module paths to `/public` URLs or inline. **MDX partials** (`_partial.mdx` imports) → inline the partial's body (Blume's default `**/_*` exclude already hides the partial files themselves).
66
66
  - **Code blocks:** `title="file.js"` → works as-is; `showLineNumbers` → `lineNumbers`; **magic comments** (`// highlight-next-line`, `highlight-start`/`end`) → `{ranges}` or `// [!code highlight]` — unconverted they ship as literal comments in every sample; ` ```bash npm2yarn ` → ` ```package-install `.
67
67
  - **MDX v1 (v2 sources) pitfalls:** unescaped `<`/`{` in prose, HTML comments `<!-- -->` (→ `{/* */}`), string `style="…"` attributes (→ objects). Fix as build errors surface.
@@ -71,7 +71,7 @@ A Docusaurus `blog/` → Blume `type: blog` pages. **Dates come from filenames/f
71
71
  | Docusaurus | Blume |
72
72
  | --- | --- |
73
73
  | `title` / `description` | pass through |
74
- | `id` | usually drop (routing is filesystem-based); use `slug` to pin a route |
74
+ | `id` | **not a safe drop** — Docusaurus builds a doc's URL from its ID, so `id: part1` in `guides/hello.md` serves `/docs/guides/part1`. Where the `id` differs from the filename (and no `slug` is set), replace it with `slug: guides/part1` (the folder path plus the id) or rename the file to match; otherwise drop it. Sidebar and `link.type: 'doc'` references name docs by this ID too |
75
75
  | `slug` | `slug` as a **full path from the content root**: Blume's `slug` replaces the page's whole route, while Docusaurus resolves a relative slug (no leading `/`) against the doc's folder. So `guides/intro.md` with `slug: start` → `slug: guides/start`; an absolute slug (`/start`) is already a full path |
76
76
  | `sidebar_label` | `sidebar.label` |
77
77
  | `sidebar_position` | `sidebar.order` |
@@ -6,21 +6,22 @@ Fumadocs is code-first: navigation comes from the folder tree + per-folder `meta
6
6
 
7
7
  - Content under **`content/docs/`** (the Fumadocs convention).
8
8
  - Per-folder **`meta.json`** files.
9
- - `fumadocs-ui` / `fumadocs-core` / `fumadocs-mdx` deps and a `source.config.ts`.
10
- - A `loader({ baseUrl: "/docs" })` call in `lib/source.ts` (or `app/source.ts`, `src/lib/source.ts`, `source.ts`).
9
+ - `fumadocs-ui` / `fumadocs-core` / `fumadocs-mdx` deps, and collections declared either in a `source.config.ts` (the Config API) or, in newer scaffolds, with `defineDocs` from `fumadocs-mdx/macro` inside `lib/source.ts` (no `source.config.ts` at all).
10
+ - A `loader({ baseUrl })` call in `lib/source.ts` (or `app/source.ts`, `src/lib/source.ts`, `source.ts`). Newer scaffolds pass a constant (`baseUrl: docsRoute`) defined in `lib/shared.ts` — resolve it there.
11
11
 
12
12
  ## Config
13
13
 
14
- Fumadocs declares almost nothing Blume needs — only two things map:
14
+ Fumadocs declares little Blume needs — read the loader and the shared layout options:
15
15
 
16
- - **Title** — read `package.json` `name`, prettified (drop scope, split on `-_`, Title-Case). For a generic monorepo name (`web`, `app`, `docs`…), use the repo-root directory name instead.
16
+ - **Title** — the header title: `nav.title` in `baseOptions` (`lib/layout.shared.tsx`, or `app/layout.config.tsx` in older scaffolds; often the `appName` constant from `lib/shared.ts`; extract the text if it's JSX). Without one, read `package.json` `name`, prettified (drop scope, split on `-_`, Title-Case); for a generic monorepo name (`web`, `app`, `docs`…), use the repo-root directory name instead.
17
+ - **Header settings** (the same `baseOptions`): `githubUrl` → `github: { owner, repo }` (the repository and edit links; when it's built from a `gitConfig` constant in `lib/shared.ts`, `{ user, repo, branch }` → `{ owner: user, repo, branch }`); `links` entries → `navigation.actions` (`{ label: text, href: url }`), except a `type: "icon"` link to a platform `footer.socials` knows (Discord, X, …), which goes there instead. Menus, custom items, and JSX in `nav` → report.
17
18
  - **Route prefix** — the `baseUrl` in the source loader (required in Fumadocs, so it's always declared). If it's a real prefix (e.g. `/docs`), set top-level **`basePath: "/docs"`** — it prefixes every route while staying invisible to the sidebar, which matches Fumadocs' behavior. (A `content.sources` `prefix` also works but adds a wrapping nav group — only use it when you _want_ the group.) If `baseUrl` is `"/"`, serve from the site root (nothing to set).
18
19
 
19
- Everything else is `defineConfig({ title })`.
20
+ Everything else is Blume defaults.
20
21
 
21
- **Also read `source.config.ts` `mdxOptions`** — remark/rehype plugins live there:
22
+ **Also read the MDX options** — remark/rehype plugins live in `source.config.ts` `mdxOptions`, or, with the macro API, in the collection options passed to `defineDocs` (a macro project may still keep a `source.config.ts` for global options):
22
23
 
23
- - `remark-math`/`rehype-katex` → nothing to configure: block math `$$…$$` renders in `.mdx` out of the box (there is **no** `markdown.math` field). Inline `$…$` is **not** supported — convert inline math to `$$…$$` or drop it (report).
24
+ - `remark-math`/`rehype-katex` → nothing to configure: `$$…$$` renders in `.mdx` out of the box (there is **no** `markdown.math` field), as a block or inline within a sentence. A single `$` stays literal, so convert each inline `$…$` to `$$…$$` inside its sentence.
24
25
  - A Twoslash transformer → Blume supports the `twoslash` fence meta natively; drop the plugin.
25
26
  - Custom Shiki transformers/themes or other plugins → report.
26
27
 
@@ -39,14 +40,17 @@ Everything else is `defineConfig({ title })`.
39
40
  | `description` | **drop** (folders have no description) |
40
41
  | `collapsible: false` | that folder's `display: "flat"` (a plain heading, even under a global `"group"` mode) |
41
42
 
42
- `meta.ts` accepts **only** `title`, `icon`, `order`, `collapsed`, `pages`, `display`. Render mode is per-folder or global: a folder that needs collapsible rendering sets its own `meta.ts` `display: "group"` (drill-in is `"page"`); when the whole sidebar should collapse, set `navigation.sidebar.display: "group"` once in `blume.config.ts` instead of repeating it per folder.
43
+ `meta.ts` accepts **only** `title`, `icon`, `order`, `collapsed`, `pages`, `display`, `directory` (`"card"`/`"accordion"` lists the folder's pages below its `index` page — the replacement for a hand-written card list of the folder's children). Render mode is per-folder or global: a folder that needs collapsible rendering sets its own `meta.ts` `display: "group"` (drill-in is `"page"`); when the whole sidebar should collapse, set `navigation.sidebar.display: "group"` once in `blume.config.ts` instead of repeating it per folder.
44
+
45
+ **Numeric filename prefixes move URLs.** Fumadocs builds slugs from the file path as written, so `01-intro.mdx` serves `/docs/01-intro`; Blume reads a `01-` prefix on a file or folder as sort order and drops it from the route (`/docs/intro`). Add a `redirects` entry for every prefixed file or folder (or pin the old path with frontmatter `slug`, a full path from the content root), and write `pages` entries without the prefix (`"intro"`, not `"01-intro"`).
43
46
 
44
47
  **`root: true` folders are Fumadocs' tab mechanism** — Fumadocs UI renders them as layout tabs and scopes the sidebar to the active one. That is exactly Blume's `navigation.tabs`: add `{ label, path, icon? }` per root folder (label/icon from its `meta.json`), pointing at the folder's route. Blume then scopes the sidebar by URL prefix the same way. Don't try to model root folders inside `meta.ts`.
45
48
 
46
49
  Handle the `pages` array items:
47
50
 
48
51
  - **`"..."`** (rest marker) / `""` → drop; Blume appends unlisted pages automatically. **`"z...a"`** (reversed rest) → no equivalent; list the pages explicitly in the intended order.
49
- - **`"!page"`** (exclusion) → Blume _appends_ unlisted pages, so a dropped `!page` **resurfaces in the sidebar**. Set that page's frontmatter `sidebar.hidden: true` instead.
52
+ - **A `pages` list with no `"..."`** → Fumadocs shows **only** the listed items; every page and folder it leaves out is hidden from the sidebar (still served by URL). Blume appends unlisted children, so each one **resurfaces** — set `sidebar.hidden: true` in each left-out page's frontmatter (for a left-out folder, in every page under it — a folder with no visible page drops out of the sidebar).
53
+ - **`"!page"`** (exclusion from `"..."`) → the same trap: Blume _appends_ unlisted pages, so a dropped `!page` **resurfaces in the sidebar**. Set that page's frontmatter `sidebar.hidden: true` instead.
50
54
  - **`"---Section---"`** (separator; also the `---[Icon]Label---` variant) → Blume has no flat separator. Turn each section into a **`(Section)/` group folder** (route-transparent — the `(…)` segment is stripped from URLs), and move the section's pages into it. If a section wraps a single existing folder, leave it in place and set that folder's `meta.ts` `title` instead.
51
55
  - **`"...folder"`** (extract) → Blume can't flatten a folder inline; keep it as a normal group at that ordering position and report it.
52
56
  - **`"[Text](url)"`** (link; also `[Icon][Text](url)` and `external:` variants) → a top-level/utility link belongs in **`navigation.featured`** (`{ label, href, icon? }` — pinned above the sidebar on every route). A link buried deep in one folder has no folder-meta home — drop and report (or model that one folder via an explicit `navigation.sidebar`).
@@ -54,7 +58,7 @@ Handle the `pages` array items:
54
58
 
55
59
  ## Frontmatter
56
60
 
57
- Fumadocs' core frontmatter (`title`, `description`, `icon`) matches Blume — but **convert `icon` casing** (see Icons). Drops: **`full`** (Fumadocs' full-width/no-TOC layout — no equivalent; report) and **`_openapi`** (generated API stubs — delete the whole page, see OpenAPI). Any other non-schema key is a build error, so drop and report.
61
+ Fumadocs' core frontmatter (`title`, `description`, `icon`) matches Blume — but **convert `icon` casing** (see Icons). **`full: true`** (full width, TOC folded into a popover) → `mode: wide` (full column width, no TOC — the closest match; report); **`_openapi`** marks a generated API stub — delete the whole page (see OpenAPI). Any other non-schema key is a build error, so drop and report.
58
62
 
59
63
  ## Icons: lucide-react names → kebab-case
60
64
 
@@ -71,7 +75,7 @@ Fumadocs icons are strings resolved by the repo's own `icon` handler in `loader(
71
75
  - **`<Banner>`** (layout-mounted, not per-page) → the `banner` config field (`{ content, link: { href, text }, dismissible, id }`).
72
76
  - **`<include>./partial.mdx</include>`** — Blume uses the same include syntax. **Pass through unchanged**: paths resolve relative to the including file, a leading `/` resolves from the content root, and nested includes work. Keep partials in `_`-prefixed files or folders so they stay out of routing and the sidebar.
73
77
  - **No equivalent — report:** `<DynamicCodeBlock>`, `<ImageZoom>` (Blume zooms content images by default), `<InlineTOC>`.
74
- - **Strip or convert every import** — not just `fumadocs-*`: `lucide-react` imports (icon JSX → string names), `next/image`/`next/link` (→ Markdown image/link), and local components. **Inventory `mdx-components.tsx` before deleting it** — components registered there are used import-free in MDX bodies; port or inline each usage first.
78
+ - **Strip or convert every import** — not just `fumadocs-*`: `lucide-react` imports (icon JSX → string names), `next/image`/`next/link` (→ Markdown image/link), and local components. **Inventory the MDX components file before deleting it** — `components/mdx.tsx` (`getMDXComponents`) in newer scaffolds, `mdx-components.tsx` in older ones; components registered there are used import-free in MDX bodies; port or inline each usage first.
75
79
 
76
80
  ## Headings
77
81
 
@@ -96,8 +100,8 @@ A `loader({ i18n })` setup (locale-suffixed files or locale dirs) → Blume `i18
96
100
 
97
101
  ## Package.json & teardown
98
102
 
99
- Repoint scripts (`dev`→`blume dev`, `build`→`blume build`, `start`→`blume preview`), remove the `fumadocs-*`/`@fumadocs/*` deps and the host framework's deps (`next`, `react-router`, `@tanstack/*`…), add `blume`. Safe to delete after harvesting (see above for what to read first): `source.config.*`, `mdx-components.tsx`, the app/route dir, and host-framework config (`next.config.*`, `next-env.d.ts`, the `next` tsconfig plugin — or the React Router/TanStack equivalents).
103
+ Repoint scripts (`dev`→`blume dev`, `build`→`blume build`, `start`→`blume preview`), remove the `fumadocs-*`/`@fumadocs/*` deps and the host framework's deps (`next`, `react-router`, `@tanstack/*`…), add `blume`. Safe to delete after harvesting (see above for what to read first): `source.config.*`, `lib/source.ts`, `lib/shared.ts`, `lib/layout.shared.tsx` (or `app/layout.config.tsx`), `components/mdx.tsx` / `mdx-components.tsx`, the app/route dir, and host-framework config (`next.config.*`, `next-env.d.ts`, the `next` tsconfig plugin — or the React Router/TanStack equivalents).
100
104
 
101
105
  ## Dropped — report these
102
106
 
103
- Folder `description` and `collapsible: false`; reversed rest (`z...a`) ordering; the extract (`...folder`) flatten semantics; deep-folder sidebar links; frontmatter `full`; `<DynamicCodeBlock>`/`<InlineTOC>`; custom Shiki transformers; any icon with no Lucide equivalent after casing conversion.
107
+ Folder `description` and `collapsible: false`; reversed rest (`z...a`) ordering; the extract (`...folder`) flatten semantics; deep-folder sidebar links; frontmatter `full` (approximated as `mode: wide`); header settings from `lib/layout.shared.tsx` with no Blume home; `<DynamicCodeBlock>`/`<InlineTOC>`; custom Shiki transformers; any icon with no Lucide equivalent after casing conversion.
@@ -35,14 +35,17 @@ For each `_meta` entry (`key` = slug, value = string title or `{ title, type, di
35
35
  | `href` (external link) | root-level → **`navigation.actions`** (`[{ label, href }]`, plain header links, matching Nextra's navbar placement) or **`navigation.featured`** (`{ label, href, icon? }` — pinned above the sidebar on every route, survives on phones); only drop deep-nested ones (report) |
36
36
  | `type: "page"` (subfolder, not root) | drop (only root → tabs) |
37
37
  | `theme: { collapsed }` on a folder | `meta.ts` `collapsed` |
38
- | `theme: { layout: "full" \| sidebar: false \| … }` | drop (report — no per-page layout switches) |
38
+ | `theme: { layout: "full" }` on a page | that page's frontmatter `mode: wide` (full column width, no table of contents — the closest match; report) |
39
+ | other `theme` keys (`sidebar: false`, `toc: false`, `typesetting`, …) | drop (report — no per-page switches for them) |
39
40
  | `"*"` wildcard entry | apply its value as the default for unlisted siblings, then drop |
40
41
 
41
42
  Write each folder's `meta.ts` with its `pages` order (from its own `_meta`) and its `title` (inherited from the parent's `_meta`). A v4 **`_meta.global.*`** file holds the whole tree in one place (folder entries carry `items`) — split it into per-folder `meta.ts` files with the same rules.
42
43
 
44
+ **Keep the order with `display: "group"`.** Nextra draws folders as collapsible rows and keeps pages and folders interleaved as `_meta` lists them. Blume's default `flat` display draws a folder as a plain section header, so it lists each level's pages above its subfolders — a page `_meta` puts after a folder jumps above it. Set `navigation.sidebar.display: "group"` (or `display: "group"` in the `meta.ts` of every subfolder at a level whose order must hold — one flat sibling folder reorders the level) to keep the authored order in nested folders. Groups then start closed unless they hold the current page, while Nextra opens every folder shallower than its `defaultMenuCollapseLevel` (default `2`: the top-level folders), so add `collapsed: false` to those folders' `meta.ts`. The top level (and each tab's top level) lists loose pages before folders in every display mode, so a root `_meta` that puts a page after a folder can't be matched — report it.
45
+
43
46
  ## Frontmatter & titles — synthesize, don't just pass through
44
47
 
45
- Most Nextra pages have **no frontmatter**; the title falls back `_meta` title → `sidebarTitle` → `title` → **first body H1** → filename. Blume renders frontmatter `title` as the page H1, so for **every page**: set frontmatter `title` (from the `_meta` title, else the body H1, else a humanized filename), **remove the body H1**, and map `sidebarTitle` → `sidebar.label`. `asIndexPage: true` (v4 folder-index marker) → make the file the folder's `index.mdx` and drop the key. Drop any other non-schema key and report it.
48
+ Most Nextra pages have **no frontmatter**. Nextra keeps two titles apart: the **sidebar label** falls back `_meta` title → `sidebarTitle` → `title` → first body H1 → filename, while the **heading readers see** is the body H1. Blume renders frontmatter `title` as the page H1, so for **every page**: set frontmatter `title` from the body H1 (else frontmatter `title`, else the `_meta` title, else a humanized filename), **remove the body H1**, and set `sidebar.label` to the `_meta` title (else `sidebarTitle`) wherever it differs from `title`. Don't take the `_meta` label as `title` — that renames the page's heading. `asIndexPage: true` (v4 folder-index marker) → make the file the folder's `index.mdx` and drop the key. Drop any other non-schema key and report it.
46
49
 
47
50
  ## Components
48
51
 
@@ -60,12 +63,12 @@ Nextra's fence meta differs from Blume's — rewrite it: `filename="app.js"` →
60
63
 
61
64
  ## Math
62
65
 
63
- Nextra enables math via `nextra({ latex: true })` (KaTeX or MathJax). In Blume, block math `$$…$$` renders in `.mdx` with **no config** (there is no `markdown.math` field). Convert Nextra's ` ```math ` fences → `$$…$$` blocks (they render as plain code blocks otherwise). Inline `$…$` is **not** supported — convert to display math or drop (report). MathJax-specific macros → report.
66
+ Nextra enables math via `nextra({ latex: true })` (KaTeX or MathJax). In Blume, block math `$$…$$` renders in `.mdx` with **no config** (there is no `markdown.math` field). Convert Nextra's ` ```math ` fences → `$$…$$` blocks (they render as plain code blocks otherwise). Inline math is `$$…$$` too: a single `$` stays literal, so convert each inline `$…$` to `$$…$$` inside its sentence. MathJax-specific macros → report.
64
67
 
65
68
  ## i18n
66
69
 
67
70
  - **v2/v3:** locale **file suffixes** (`index.en.mdx`, `index.zh.mdx`) + `i18n` in `next.config` → match Blume's `dot` parser as-is, default-locale suffix included: `i18n: { defaultLocale, locales: [{ code, label }], parser: "dot" }`, no file moves. Page titles land in each file's own frontmatter, so they stay per-locale, but under `dot` a folder's `meta.ts` serves every locale — build it from the default locale's `_meta` and report translated folder titles from the other locales' `_meta` files.
68
- - **v4:** `content/<lang>/` dirs already match Blume's `dir` parser — map the locale list, no file moves.
71
+ - **v4:** every locale has a `content/<lang>/` dir, the default included, and Nextra serves every locale under its prefix (`/en/…`). Blume's `dir` parser takes the other locales' dirs as-is, but the default locale lives **at the content root**: a `content/en/` dir is ordinary content that publishes at `/en/…` and warns `BLUME_I18N_DEFAULT_LOCALE_FOLDER`. Move the default locale's files (and `_meta`-derived `meta.ts` files) up one level, and set `i18n.hideDefaultLocalePrefix: false` to keep serving them at `/en/…` as Nextra did — or keep Blume's unprefixed default and add a `redirects` entry per page.
69
72
 
70
73
  ## Package.json & teardown
71
74
 
@@ -73,4 +76,4 @@ Repoint `dev`/`build`/`start` scripts to the Blume CLI; remove `next`/`nextra`/`
73
76
 
74
77
  ## Dropped — report these
75
78
 
76
- Footer content; `primaryHue`-style theming beyond an accent color; `faviconGlyph`; `_meta` separators, menus, `newWindow`; per-page `theme` layout switches; `<Bleed>`; MathJax macros; word-highlight/copy fence meta; any icon you can't reconstruct as a Lucide name.
79
+ Footer content; `primaryHue`-style theming beyond an accent color; `faviconGlyph`; `_meta` separators, menus, `newWindow`; per-page `theme` switches (and `layout: "full"` approximated as `mode: wide`); root-level pages `_meta` ordered after a folder; `<Bleed>`; MathJax macros; word-highlight/copy fence meta; any icon you can't reconstruct as a Lucide name.