blume 2.1.0 → 2.1.2

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 (450) hide show
  1. package/CHANGELOG.md +110 -0
  2. package/dist/cli/{chunk-ga0pf4aj.js → chunk-0n9xkp3d.js} +12 -3
  3. package/dist/cli/{chunk-ga0pf4aj.js.map → chunk-0n9xkp3d.js.map} +3 -3
  4. package/dist/cli/{chunk-43zcf4gz.js → chunk-0pmb4tr4.js} +541 -368
  5. package/dist/cli/chunk-0pmb4tr4.js.map +35 -0
  6. package/dist/cli/{chunk-9geymqns.js → chunk-0w6hs479.js} +15 -15
  7. package/dist/cli/{chunk-xkz5xbtv.js → chunk-124fvmzq.js} +2802 -1422
  8. package/dist/cli/chunk-124fvmzq.js.map +220 -0
  9. package/dist/cli/chunk-150jk7km.js +52 -0
  10. package/dist/cli/chunk-150jk7km.js.map +10 -0
  11. package/dist/cli/{chunk-y4ykbark.js → chunk-1f882900.js} +2 -2
  12. package/dist/cli/{chunk-5ffywsmb.js → chunk-297ajdcv.js} +8 -6
  13. package/dist/cli/{chunk-5ffywsmb.js.map → chunk-297ajdcv.js.map} +3 -3
  14. package/dist/cli/{chunk-h2a48yhd.js → chunk-32khwcb6.js} +120 -16
  15. package/dist/cli/{chunk-h2a48yhd.js.map → chunk-32khwcb6.js.map} +10 -10
  16. package/dist/cli/{chunk-5cg2rzgf.js → chunk-36bxs9c7.js} +20 -49
  17. package/dist/cli/{chunk-5cg2rzgf.js.map → chunk-36bxs9c7.js.map} +4 -5
  18. package/dist/cli/{chunk-py62fewh.js → chunk-53jqw8z0.js} +3 -3
  19. package/dist/cli/{chunk-5wj2qwn2.js → chunk-58bt3hag.js} +4 -4
  20. package/dist/cli/{chunk-e7vvk5ya.js → chunk-5cmts6dv.js} +3 -3
  21. package/dist/cli/{chunk-y5h4r2y0.js → chunk-60hz265r.js} +15 -5
  22. package/dist/cli/chunk-60hz265r.js.map +10 -0
  23. package/dist/cli/chunk-74wfhv79.js +65 -0
  24. package/dist/cli/chunk-74wfhv79.js.map +10 -0
  25. package/dist/cli/{chunk-g2z9x01v.js → chunk-9qzgg6mj.js} +3 -3
  26. package/dist/cli/{chunk-q3wxxq39.js → chunk-a57yh0h9.js} +5 -5
  27. package/dist/cli/{chunk-713mngdq.js → chunk-ave1wcbr.js} +76 -10
  28. package/dist/cli/chunk-ave1wcbr.js.map +14 -0
  29. package/dist/cli/{chunk-2n4ps1b3.js → chunk-bqxaqn5p.js} +40 -22
  30. package/dist/cli/chunk-bqxaqn5p.js.map +15 -0
  31. package/dist/cli/{chunk-jnwj5kjj.js → chunk-bxwhtvw8.js} +20 -7
  32. package/dist/cli/{chunk-jnwj5kjj.js.map → chunk-bxwhtvw8.js.map} +3 -3
  33. package/dist/cli/chunk-ckrcm08h.js +324 -0
  34. package/dist/cli/chunk-ckrcm08h.js.map +14 -0
  35. package/dist/cli/{chunk-2f3ndkpw.js → chunk-desvm72z.js} +3 -3
  36. package/dist/cli/{chunk-gp84za0h.js → chunk-dhwtqnnd.js} +2 -2
  37. package/dist/cli/{chunk-4h8z9vv5.js → chunk-dpy8qdjf.js} +93 -47
  38. package/dist/cli/chunk-dpy8qdjf.js.map +17 -0
  39. package/dist/cli/{chunk-2f7n4qzt.js → chunk-dqhg54mr.js} +1 -1
  40. package/dist/cli/{chunk-0gz36akr.js → chunk-dv2z0rzf.js} +21 -5
  41. package/dist/cli/chunk-dv2z0rzf.js.map +12 -0
  42. package/dist/cli/{chunk-cm89scdv.js → chunk-es8h0v3j.js} +9 -11
  43. package/dist/cli/{chunk-cm89scdv.js.map → chunk-es8h0v3j.js.map} +3 -3
  44. package/dist/cli/{chunk-zhvd6adm.js → chunk-gfc5fqvf.js} +14 -14
  45. package/dist/cli/{chunk-nd1821sk.js → chunk-gxtg6ed2.js} +33 -19
  46. package/dist/cli/chunk-gxtg6ed2.js.map +10 -0
  47. package/dist/cli/{chunk-p804f3pe.js → chunk-hvcxng5f.js} +6 -6
  48. package/dist/cli/{chunk-ta27feev.js → chunk-j1x92mx9.js} +21 -2
  49. package/dist/cli/chunk-j1x92mx9.js.map +10 -0
  50. package/dist/cli/{chunk-rekqz4qg.js → chunk-k3gr1nm9.js} +19 -16
  51. package/dist/cli/chunk-k3gr1nm9.js.map +10 -0
  52. package/dist/cli/{chunk-1n228xqb.js → chunk-kdpzw38a.js} +2 -2
  53. package/dist/cli/{chunk-cy5bm0j0.js → chunk-ms9z66nz.js} +2 -2
  54. package/dist/cli/{chunk-cy5bm0j0.js.map → chunk-ms9z66nz.js.map} +2 -2
  55. package/dist/cli/{chunk-zf748avv.js → chunk-nbyvgnnm.js} +193 -286
  56. package/dist/cli/chunk-nbyvgnnm.js.map +36 -0
  57. package/dist/cli/{chunk-6bm0xpgd.js → chunk-s0bdnc7v.js} +2 -2
  58. package/dist/cli/{chunk-yxmk4pec.js → chunk-sc93apfs.js} +17 -16
  59. package/dist/cli/{chunk-yxmk4pec.js.map → chunk-sc93apfs.js.map} +3 -3
  60. package/dist/cli/{chunk-cmtcnh9f.js → chunk-tfycddbt.js} +2 -2
  61. package/dist/cli/{chunk-9xr4dgee.js → chunk-twy7psdz.js} +29 -16
  62. package/dist/cli/chunk-twy7psdz.js.map +10 -0
  63. package/dist/cli/{chunk-ntv4qrt7.js → chunk-y4msrwm9.js} +15 -15
  64. package/dist/cli/{chunk-yj17vs39.js → chunk-ydp3065p.js} +5 -5
  65. package/dist/cli/index.js +24 -19
  66. package/dist/cli/index.js.map +3 -3
  67. package/dist/types/ai/ask-context.d.ts +6 -2
  68. package/dist/types/ai/ask.d.ts +24 -5
  69. package/dist/types/ai/component-markdown.d.ts +2 -0
  70. package/dist/types/ai/openapi-components.d.ts +4 -1
  71. package/dist/types/analytics/clics.d.ts +37 -0
  72. package/dist/types/analytics/index.d.ts +2 -0
  73. package/dist/types/analytics/schema.d.ts +18 -0
  74. package/dist/types/astro/integration.d.ts +11 -4
  75. package/dist/types/astro/markdown-negotiation.d.ts +3 -0
  76. package/dist/types/astro/pages.d.ts +10 -0
  77. package/dist/types/cli/init/scaffold.d.ts +1 -1
  78. package/dist/types/components/content/api-field.d.ts +4 -1
  79. package/dist/types/components/content/api-page.d.ts +6 -1
  80. package/dist/types/components/openapi/code-samples.d.ts +29 -0
  81. package/dist/types/components/openapi/helpers.d.ts +22 -1
  82. package/dist/types/components/openapi/security.d.ts +10 -0
  83. package/dist/types/components/openapi/snippets.d.ts +60 -0
  84. package/dist/types/core/changelog-index.d.ts +8 -0
  85. package/dist/types/core/config-input.d.ts +37 -6
  86. package/dist/types/core/data.d.ts +4 -0
  87. package/dist/types/core/i18n-ui.d.ts +2 -0
  88. package/dist/types/core/links.d.ts +8 -0
  89. package/dist/types/core/powered-by.d.ts +12 -0
  90. package/dist/types/core/probe.d.ts +2 -0
  91. package/dist/types/core/project-graph.d.ts +6 -0
  92. package/dist/types/core/redirect-patterns.d.ts +11 -3
  93. package/dist/types/core/schema.d.ts +228 -3
  94. package/dist/types/core/sources/cache.d.ts +3 -0
  95. package/dist/types/core/sources/contentful-rich-text.d.ts +11 -2
  96. package/dist/types/core/sources/contentful.d.ts +17 -1
  97. package/dist/types/core/sources/lexical.d.ts +5 -0
  98. package/dist/types/core/sources/lower.d.ts +10 -2
  99. package/dist/types/core/sources/normalize.d.ts +20 -1
  100. package/dist/types/core/sources/notion.d.ts +8 -0
  101. package/dist/types/core/sources/remote.d.ts +37 -10
  102. package/dist/types/core/sources/sanity.d.ts +1 -1
  103. package/dist/types/core/types.d.ts +17 -0
  104. package/dist/types/deploy/adapter-output.d.ts +2 -1
  105. package/dist/types/deploy/adapters/cloudflare.d.ts +7 -5
  106. package/dist/types/deploy/adapters/node.d.ts +58 -23
  107. package/dist/types/deploy/adapters/registry.d.ts +10 -0
  108. package/dist/types/deploy/adapters/types.d.ts +3 -2
  109. package/dist/types/deploy/cloudflare-negotiation.d.ts +5 -2
  110. package/dist/types/deploy/node-headers.d.ts +12 -4
  111. package/dist/types/deploy/platforms/cloudflare.d.ts +10 -7
  112. package/dist/types/deploy/platforms/node.d.ts +3 -1
  113. package/dist/types/deploy/platforms/types.d.ts +18 -7
  114. package/dist/types/deploy/platforms/vercel.d.ts +21 -2
  115. package/dist/types/deploy/redirects.d.ts +17 -1
  116. package/dist/types/deploy/sitemap.d.ts +2 -1
  117. package/dist/types/deploy/vercel-negotiation.d.ts +44 -21
  118. package/dist/types/narration/build.d.ts +34 -12
  119. package/dist/types/narration/provider.d.ts +96 -8
  120. package/dist/types/openapi/checks.d.ts +27 -0
  121. package/dist/types/openapi/graphql.d.ts +6 -0
  122. package/dist/types/openapi/model.d.ts +15 -4
  123. package/dist/types/openapi/parse.d.ts +3 -0
  124. package/dist/types/openapi/references.d.ts +16 -0
  125. package/dist/types/openapi/sentence.d.ts +11 -1
  126. package/dist/types/ratelimit/index.d.ts +1 -1
  127. package/dist/types/ratelimit/schema.d.ts +8 -0
  128. package/dist/types/ratelimit/upstash.d.ts +39 -14
  129. package/dist/types/reference/graphql.d.ts +76 -1
  130. package/dist/types/reference/index.d.ts +1 -1
  131. package/dist/types/reference/openapi.d.ts +2 -2
  132. package/dist/types/reference/options.d.ts +37 -1
  133. package/dist/types/reference/schema.d.ts +84 -0
  134. package/dist/types/search/adapters/registry.d.ts +3 -0
  135. package/dist/types/search/adapters/typesense.d.ts +14 -4
  136. package/dist/types/search/build.d.ts +3 -1
  137. package/dist/types/search/orama-index.d.ts +9 -9
  138. package/dist/types/search/sync/algolia.d.ts +5 -2
  139. package/dist/types/search/sync/index.d.ts +3 -1
  140. package/dist/types/search/sync/typesense.d.ts +11 -7
  141. package/dist/types/seo/jsonld.d.ts +11 -6
  142. package/dist/types/sources/contentful.d.ts +16 -0
  143. package/dist/types/sources/github-releases.d.ts +8 -1
  144. package/dist/types/sources/notion.d.ts +8 -0
  145. package/dist/types/sources/payload.d.ts +2 -0
  146. package/dist/types/sources/registry.d.ts +63 -0
  147. package/dist/types/sources/shared.d.ts +3 -0
  148. package/dist/types/sources/strapi.d.ts +2 -0
  149. package/docs/01-quickstart.mdx +4 -2
  150. package/docs/02-deployment.mdx +59 -13
  151. package/docs/03-upgrading.mdx +2 -1
  152. package/docs/04-migrating.mdx +9 -9
  153. package/docs/08-faq.mdx +12 -12
  154. package/docs/advanced/changelog.mdx +30 -2
  155. package/docs/advanced/custom-pages.mdx +1 -1
  156. package/docs/advanced/skills.mdx +1 -1
  157. package/docs/cli/audit.mdx +30 -7
  158. package/docs/cli/doctor.mdx +6 -2
  159. package/docs/cli/evals.mdx +4 -4
  160. package/docs/cli/translate.mdx +1 -0
  161. package/docs/cli/validate.mdx +4 -3
  162. package/docs/configuration/analytics.mdx +19 -1
  163. package/docs/configuration/assistant.mdx +6 -2
  164. package/docs/configuration/customization.mdx +4 -2
  165. package/docs/configuration/export.mdx +5 -3
  166. package/docs/configuration/index.mdx +4 -2
  167. package/docs/configuration/narration.mdx +43 -6
  168. package/docs/configuration/rate-limiting.mdx +23 -2
  169. package/docs/configuration/search.mdx +37 -14
  170. package/docs/content/components.mdx +2 -2
  171. package/docs/content/frontmatter.mdx +5 -3
  172. package/docs/content/i18n.mdx +2 -2
  173. package/docs/content/includes.mdx +1 -1
  174. package/docs/content/navigation.mdx +8 -6
  175. package/docs/content/sources/contentful.mdx +9 -4
  176. package/docs/content/sources/custom.mdx +7 -3
  177. package/docs/content/sources/github-releases.mdx +12 -2
  178. package/docs/content/sources/index.mdx +3 -3
  179. package/docs/content/sources/notion.mdx +17 -2
  180. package/docs/content/sources/obsidian.mdx +3 -1
  181. package/docs/content/sources/payload.mdx +5 -2
  182. package/docs/content/sources/remote-mdx.mdx +6 -0
  183. package/docs/content/sources/sanity.mdx +4 -2
  184. package/docs/content/sources/strapi.mdx +5 -2
  185. package/docs/content/syntax.mdx +2 -2
  186. package/docs/discoverability/agent-discovery.mdx +1 -1
  187. package/docs/discoverability/index.mdx +1 -1
  188. package/docs/discoverability/json-api.mdx +2 -0
  189. package/docs/discoverability/llms-txt.mdx +1 -1
  190. package/docs/discoverability/markdown.mdx +3 -1
  191. package/docs/discoverability/mcp.mdx +6 -0
  192. package/docs/discoverability/sitemap-and-robots.mdx +1 -1
  193. package/docs/discoverability/structured-data.mdx +8 -6
  194. package/docs/references/api-pages.mdx +1 -1
  195. package/docs/references/asyncapi.mdx +5 -1
  196. package/docs/references/graphql.mdx +15 -1
  197. package/docs/references/openapi.mdx +64 -8
  198. package/package.json +9 -9
  199. package/skills/blume/SKILL.md +1 -1
  200. package/skills/blume-migrate/SKILL.md +2 -2
  201. package/skills/blume-migrate/assets/{oxfmt@0.67.0.patch → oxfmt@0.71.0.patch} +7 -7
  202. package/skills/blume-migrate/references/docusaurus.md +4 -4
  203. package/skills/blume-migrate/references/fumadocs.md +17 -13
  204. package/skills/blume-migrate/references/monorepo.md +5 -5
  205. package/skills/blume-migrate/references/nextra.md +8 -5
  206. package/skills/blume-migrate/references/starlight.md +9 -7
  207. package/src/ai/ask-context.ts +33 -3
  208. package/src/ai/ask-limits.ts +13 -0
  209. package/src/ai/ask.ts +24 -6
  210. package/src/ai/changelog-markdown.ts +11 -6
  211. package/src/ai/component-markdown.ts +1 -1
  212. package/src/ai/cors.ts +12 -3
  213. package/src/ai/mcp/query.ts +14 -6
  214. package/src/ai/openapi-components.ts +28 -4
  215. package/src/ai/relative-links.ts +4 -38
  216. package/src/analytics/clics.ts +58 -0
  217. package/src/analytics/head.ts +5 -0
  218. package/src/analytics/index.ts +2 -0
  219. package/src/analytics/schema.ts +2 -0
  220. package/src/astro/generate.ts +91 -56
  221. package/src/astro/integration.ts +72 -22
  222. package/src/astro/markdown-negotiation.ts +3 -0
  223. package/src/astro/module-types.ts +3 -1
  224. package/src/astro/pages.ts +23 -0
  225. package/src/astro/render-deps.ts +10 -5
  226. package/src/astro/runtime-deps.ts +23 -2
  227. package/src/astro/templates.ts +129 -81
  228. package/src/audit/catalog.ts +19 -3
  229. package/src/audit/checks/links.ts +14 -1
  230. package/src/audit/checks/network.ts +110 -6
  231. package/src/audit/checks/redirects.ts +6 -4
  232. package/src/audit/checks/robots.ts +58 -2
  233. package/src/audit/run.ts +3 -0
  234. package/src/audit/types.ts +2 -0
  235. package/src/audit/url.ts +14 -5
  236. package/src/blume-modules.d.ts +5 -1
  237. package/src/cli/args.ts +51 -0
  238. package/src/cli/build-failure.ts +23 -0
  239. package/src/cli/commands/audit.ts +10 -1
  240. package/src/cli/commands/dev.ts +18 -2
  241. package/src/cli/commands/doctor.ts +45 -12
  242. package/src/cli/commands/eval.ts +36 -4
  243. package/src/cli/commands/init.ts +6 -4
  244. package/src/cli/commands/validate.ts +10 -1
  245. package/src/cli/init/scaffold.ts +9 -6
  246. package/src/cli/prepare.ts +6 -3
  247. package/src/cli/required-secrets.ts +3 -2
  248. package/src/components/content/ApiPlayground.astro +6 -2
  249. package/src/components/content/api-field.ts +4 -1
  250. package/src/components/content/api-page.ts +31 -7
  251. package/src/components/content/mermaid-element.ts +15 -1
  252. package/src/components/content/view-switcher.ts +7 -4
  253. package/src/components/islands/hooks.ts +27 -1
  254. package/src/components/layout/NavTree.astro +49 -5
  255. package/src/components/layout/NavTreeScript.astro +18 -0
  256. package/src/components/layout/PageActions.astro +139 -24
  257. package/src/components/layout/PageLayout.astro +13 -3
  258. package/src/components/layout/RootLayout.astro +27 -14
  259. package/src/components/layout/Search.astro +17 -3
  260. package/src/components/layout/TableOfContents.astro +24 -17
  261. package/src/components/layout/analytics-client.ts +8 -8
  262. package/src/components/layout/head-scripts.ts +6 -1
  263. package/src/components/layout/search/algolia.ts +10 -1
  264. package/src/components/layout/search/analytics.ts +42 -14
  265. package/src/components/layout/search/endpoint.ts +48 -5
  266. package/src/components/layout/search/pagefind.ts +114 -5
  267. package/src/components/layout/search/types.ts +12 -0
  268. package/src/components/layout/search/typesense.ts +18 -6
  269. package/src/components/openapi/AsyncApiOperation.astro +14 -7
  270. package/src/components/openapi/Authorization.astro +10 -0
  271. package/src/components/openapi/GraphqlOperation.astro +5 -0
  272. package/src/components/openapi/Operation.astro +24 -1
  273. package/src/components/openapi/ParametersTable.astro +8 -4
  274. package/src/components/openapi/Playground.astro +7 -1
  275. package/src/components/openapi/RequestBody.astro +19 -2
  276. package/src/components/openapi/Responses.astro +1 -0
  277. package/src/components/openapi/SchemaTable.astro +15 -3
  278. package/src/components/openapi/async-snippets.ts +61 -5
  279. package/src/components/openapi/async.ts +123 -29
  280. package/src/components/openapi/graphql-helpers.ts +35 -12
  281. package/src/components/openapi/helpers.ts +36 -2
  282. package/src/components/openapi/message-model.ts +54 -11
  283. package/src/components/openapi/message.ts +14 -4
  284. package/src/components/openapi/operation-model.ts +16 -7
  285. package/src/components/openapi/playground-client.ts +77 -58
  286. package/src/components/openapi/playground-schema.ts +1 -1
  287. package/src/components/openapi/schema-tree.ts +44 -14
  288. package/src/components/openapi/security.ts +10 -0
  289. package/src/components/openapi/snippets.ts +5 -3
  290. package/src/components/openapi/validate-json.ts +4 -3
  291. package/src/components/powered-by-middleware.ts +33 -0
  292. package/src/core/changelog-index.ts +31 -1
  293. package/src/core/component-diagnostics.ts +53 -0
  294. package/src/core/component-overrides.ts +32 -0
  295. package/src/core/config-input.ts +42 -6
  296. package/src/core/data.ts +2 -0
  297. package/src/core/diagnostics.ts +5 -0
  298. package/src/core/i18n-ui.ts +2 -0
  299. package/src/core/includes.ts +1 -1
  300. package/src/core/links.ts +47 -17
  301. package/src/core/powered-by.ts +12 -0
  302. package/src/core/probe.ts +26 -6
  303. package/src/core/project-graph.ts +59 -18
  304. package/src/core/redirect-patterns.ts +36 -7
  305. package/src/core/schema.ts +26 -6
  306. package/src/core/sources/cache.ts +6 -0
  307. package/src/core/sources/contentful-rich-text.ts +29 -6
  308. package/src/core/sources/contentful.ts +135 -25
  309. package/src/core/sources/filesystem.ts +26 -4
  310. package/src/core/sources/github-releases.ts +12 -1
  311. package/src/core/sources/lexical.ts +15 -10
  312. package/src/core/sources/lower.ts +19 -3
  313. package/src/core/sources/mdx-remote.ts +62 -10
  314. package/src/core/sources/normalize.ts +227 -41
  315. package/src/core/sources/notion.ts +34 -10
  316. package/src/core/sources/obsidian.ts +16 -3
  317. package/src/core/sources/payload.ts +38 -15
  318. package/src/core/sources/remote.ts +72 -15
  319. package/src/core/sources/resolve.ts +11 -1
  320. package/src/core/sources/sanity.ts +43 -25
  321. package/src/core/sources/strapi.ts +2 -2
  322. package/src/core/types.ts +18 -0
  323. package/src/core/ui-packs/ar.ts +1 -0
  324. package/src/core/ui-packs/bg.ts +1 -0
  325. package/src/core/ui-packs/bn.ts +1 -0
  326. package/src/core/ui-packs/ca.ts +1 -0
  327. package/src/core/ui-packs/cs.ts +1 -0
  328. package/src/core/ui-packs/da.ts +1 -0
  329. package/src/core/ui-packs/de.ts +1 -0
  330. package/src/core/ui-packs/el.ts +1 -0
  331. package/src/core/ui-packs/es.ts +1 -0
  332. package/src/core/ui-packs/fa.ts +1 -0
  333. package/src/core/ui-packs/fi.ts +1 -0
  334. package/src/core/ui-packs/fr.ts +1 -0
  335. package/src/core/ui-packs/he.ts +1 -0
  336. package/src/core/ui-packs/hi.ts +1 -0
  337. package/src/core/ui-packs/hr.ts +1 -0
  338. package/src/core/ui-packs/hu.ts +1 -0
  339. package/src/core/ui-packs/id.ts +1 -0
  340. package/src/core/ui-packs/it.ts +1 -0
  341. package/src/core/ui-packs/ja.ts +1 -0
  342. package/src/core/ui-packs/ko.ts +1 -0
  343. package/src/core/ui-packs/nl.ts +1 -0
  344. package/src/core/ui-packs/no.ts +1 -0
  345. package/src/core/ui-packs/pl.ts +1 -0
  346. package/src/core/ui-packs/pt-br.ts +1 -0
  347. package/src/core/ui-packs/pt.ts +1 -0
  348. package/src/core/ui-packs/ro.ts +1 -0
  349. package/src/core/ui-packs/ru.ts +1 -0
  350. package/src/core/ui-packs/sk.ts +1 -0
  351. package/src/core/ui-packs/sr.ts +1 -0
  352. package/src/core/ui-packs/sv.ts +1 -0
  353. package/src/core/ui-packs/th.ts +1 -0
  354. package/src/core/ui-packs/tr.ts +1 -0
  355. package/src/core/ui-packs/uk.ts +1 -0
  356. package/src/core/ui-packs/vi.ts +1 -0
  357. package/src/core/ui-packs/zh-tw.ts +1 -0
  358. package/src/core/ui-packs/zh.ts +1 -0
  359. package/src/deploy/adapter-output.ts +7 -2
  360. package/src/deploy/adapters/cloudflare.ts +7 -5
  361. package/src/deploy/adapters/node.ts +47 -9
  362. package/src/deploy/adapters/types.ts +7 -3
  363. package/src/deploy/artifacts.ts +2 -1
  364. package/src/deploy/cloudflare-negotiation.ts +36 -7
  365. package/src/deploy/headers.ts +15 -1
  366. package/src/deploy/node-headers.ts +32 -20
  367. package/src/deploy/platforms/cloudflare.ts +14 -9
  368. package/src/deploy/platforms/netlify.ts +2 -1
  369. package/src/deploy/platforms/node.ts +12 -2
  370. package/src/deploy/platforms/types.ts +21 -7
  371. package/src/deploy/platforms/vercel.ts +115 -17
  372. package/src/deploy/redirects.ts +66 -6
  373. package/src/deploy/rss.ts +3 -1
  374. package/src/deploy/sitemap.ts +39 -5
  375. package/src/deploy/vercel-negotiation.ts +325 -103
  376. package/src/eval/agents.ts +18 -1
  377. package/src/eval/prompts.ts +6 -3
  378. package/src/eval/report.ts +34 -13
  379. package/src/eval/run.ts +5 -0
  380. package/src/markdown/base-links.ts +15 -14
  381. package/src/markdown/package-commands.ts +9 -8
  382. package/src/markdown/relative-links.ts +3 -2
  383. package/src/narration/build.ts +218 -40
  384. package/src/narration/provider.ts +89 -9
  385. package/src/openapi/checks.ts +273 -0
  386. package/src/openapi/graphql-build.ts +21 -0
  387. package/src/openapi/graphql.ts +11 -0
  388. package/src/openapi/model.ts +15 -3
  389. package/src/openapi/parse.ts +127 -2
  390. package/src/openapi/references.ts +43 -3
  391. package/src/openapi/render-mdx.ts +68 -8
  392. package/src/openapi/sentence.ts +29 -2
  393. package/src/openapi/source.ts +39 -14
  394. package/src/ratelimit/index.ts +1 -1
  395. package/src/ratelimit/runtime.ts +37 -19
  396. package/src/ratelimit/upstash.ts +40 -10
  397. package/src/reference/graphql.ts +15 -1
  398. package/src/reference/index.ts +1 -0
  399. package/src/reference/openapi.ts +2 -2
  400. package/src/reference/options.ts +29 -1
  401. package/src/registry/eject.ts +8 -2
  402. package/src/search/adapters/locale-scope.ts +30 -0
  403. package/src/search/adapters/typesense.ts +14 -4
  404. package/src/search/build.ts +21 -3
  405. package/src/search/orama-index.ts +163 -31
  406. package/src/search/source-pages.ts +29 -0
  407. package/src/search/sync/algolia.ts +99 -9
  408. package/src/search/sync/index.ts +52 -13
  409. package/src/search/sync/typesense.ts +72 -27
  410. package/src/seo/jsonld.ts +38 -20
  411. package/src/sources/contentful.ts +12 -0
  412. package/src/sources/github-releases.ts +7 -1
  413. package/src/sources/notion.ts +7 -0
  414. package/src/sources/shared.ts +3 -0
  415. package/src/theme/color.ts +9 -10
  416. package/src/theme/entry.ts +43 -6
  417. package/src/translate/validate.ts +23 -1
  418. package/dist/cli/chunk-0gz36akr.js.map +0 -12
  419. package/dist/cli/chunk-2n4ps1b3.js.map +0 -15
  420. package/dist/cli/chunk-43zcf4gz.js.map +0 -36
  421. package/dist/cli/chunk-4ejxbzee.js +0 -39
  422. package/dist/cli/chunk-4ejxbzee.js.map +0 -10
  423. package/dist/cli/chunk-4h8z9vv5.js.map +0 -16
  424. package/dist/cli/chunk-713mngdq.js.map +0 -14
  425. package/dist/cli/chunk-9xr4dgee.js.map +0 -10
  426. package/dist/cli/chunk-amrb6hgh.js +0 -145
  427. package/dist/cli/chunk-amrb6hgh.js.map +0 -11
  428. package/dist/cli/chunk-nd1821sk.js.map +0 -10
  429. package/dist/cli/chunk-rekqz4qg.js.map +0 -10
  430. package/dist/cli/chunk-ta27feev.js.map +0 -10
  431. package/dist/cli/chunk-xkz5xbtv.js.map +0 -213
  432. package/dist/cli/chunk-y5h4r2y0.js.map +0 -10
  433. package/dist/cli/chunk-zf748avv.js.map +0 -37
  434. /package/dist/cli/{chunk-9geymqns.js.map → chunk-0w6hs479.js.map} +0 -0
  435. /package/dist/cli/{chunk-y4ykbark.js.map → chunk-1f882900.js.map} +0 -0
  436. /package/dist/cli/{chunk-py62fewh.js.map → chunk-53jqw8z0.js.map} +0 -0
  437. /package/dist/cli/{chunk-5wj2qwn2.js.map → chunk-58bt3hag.js.map} +0 -0
  438. /package/dist/cli/{chunk-e7vvk5ya.js.map → chunk-5cmts6dv.js.map} +0 -0
  439. /package/dist/cli/{chunk-g2z9x01v.js.map → chunk-9qzgg6mj.js.map} +0 -0
  440. /package/dist/cli/{chunk-q3wxxq39.js.map → chunk-a57yh0h9.js.map} +0 -0
  441. /package/dist/cli/{chunk-2f3ndkpw.js.map → chunk-desvm72z.js.map} +0 -0
  442. /package/dist/cli/{chunk-gp84za0h.js.map → chunk-dhwtqnnd.js.map} +0 -0
  443. /package/dist/cli/{chunk-2f7n4qzt.js.map → chunk-dqhg54mr.js.map} +0 -0
  444. /package/dist/cli/{chunk-zhvd6adm.js.map → chunk-gfc5fqvf.js.map} +0 -0
  445. /package/dist/cli/{chunk-p804f3pe.js.map → chunk-hvcxng5f.js.map} +0 -0
  446. /package/dist/cli/{chunk-1n228xqb.js.map → chunk-kdpzw38a.js.map} +0 -0
  447. /package/dist/cli/{chunk-6bm0xpgd.js.map → chunk-s0bdnc7v.js.map} +0 -0
  448. /package/dist/cli/{chunk-cmtcnh9f.js.map → chunk-tfycddbt.js.map} +0 -0
  449. /package/dist/cli/{chunk-ntv4qrt7.js.map → chunk-y4msrwm9.js.map} +0 -0
  450. /package/dist/cli/{chunk-yj17vs39.js.map → chunk-ydp3065p.js.map} +0 -0
@@ -38,9 +38,11 @@ navigation: {
38
38
  }
39
39
  ```
40
40
 
41
+ [Generate GraphQL docs from your schema](/guides/graphql-documentation-website) walks through it from a schema to a deployed site, with a Try it panel that queries a sandbox.
42
+
41
43
  ## Generated examples
42
44
 
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:
45
+ 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
46
 
45
47
  ```ts blume.config.ts lineNumbers
46
48
  reference: [
@@ -82,6 +84,18 @@ reference: [
82
84
 
83
85
  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
86
 
87
+ 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:
88
+
89
+ ```ts blume.config.ts lineNumbers
90
+ reference: [
91
+ graphql({
92
+ spec: "./schema.graphql",
93
+ endpoint: "https://api.example.com/graphql",
94
+ auth: { method: "bearer" },
95
+ }),
96
+ ],
97
+ ```
98
+
85
99
  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
100
 
87
101
  ```ts blume.config.ts lineNumbers
@@ -18,7 +18,9 @@ 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
+
23
+ To go from your framework's generated spec to a deployed reference, follow the guide for [FastAPI](/guides/fastapi-documentation-website), [Django REST Framework](/guides/django-rest-framework-api-documentation), [NestJS](/guides/nestjs-api-documentation-website), [Spring Boot](/guides/spring-boot-api-documentation), [Hono](/guides/hono-zod-api-documentation), [Fastify](/guides/fastify-api-documentation), [Elysia](/guides/elysia-api-documentation), or [oRPC](/guides/orpc-openapi-documentation). [Generate API docs from an OpenAPI spec](/guides/openapi-documentation-website) starts from any spec file.
22
24
 
23
25
  The reference doesn't add a header tab on its own. To surface it, point a [navigation tab](/docs/content/navigation#tabs) at its route — this also scopes the operations sidebar for the native renderer:
24
26
 
@@ -28,18 +30,30 @@ navigation: {
28
30
  }
29
31
  ```
30
32
 
33
+ The sidebar's tag groups and the overview page's sections follow the same order: the order of the spec's top-level `tags` list, then any tag it doesn't declare, in the order operations first use it.
34
+
31
35
  :::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.
36
+ Operations are indexed for search by their **summary, description, tag, and endpoint** (`GET /pets/{id}`). The rendered schema tables and generated code samples aren't full-text indexed; search matches an operation's title, prose, section, and path, then links to its own page. [Your own samples](#your-own-samples) are indexed like any code block, only with [`search.indexing.includeCodeBlocks`](/docs/configuration/search).
33
37
  :::
34
38
 
35
39
  ## A local spec
36
40
 
37
- A relative path is resolved from your project root and read at build time. Both JSON and YAML work:
41
+ 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
42
 
39
43
  ```ts blume.config.ts lineNumbers
40
44
  reference: [openapi({ spec: "./openapi.yaml" })],
41
45
  ```
42
46
 
47
+ ## OpenAPI 3.2
48
+
49
+ 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:
50
+
51
+ - `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).
52
+ - `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.
53
+ - `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.
54
+ - Examples written with `dataValue` or `serializedValue` in place of `value`: the page doesn't show them.
55
+ - Nested tags (`parent`) and tag `kind`: tags stay one flat list in the sidebar and the overview.
56
+
43
57
  ## Route
44
58
 
45
59
  `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 +69,7 @@ reference: [
55
69
 
56
70
  ## Code samples and schemas
57
71
 
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:
72
+ `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
73
 
60
74
  ```ts blume.config.ts lineNumbers
61
75
  reference: [
@@ -92,6 +106,8 @@ Every sample is plain, copy-pasteable code for the request, written with the lan
92
106
 
93
107
  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
108
 
109
+ Schema tables follow the way the data travels, as the examples do: a request body leaves out `readOnly` properties, which the server sets, and a response leaves out `writeOnly` ones, which it never returns. A [webhook or callback](#webhooks-and-callbacks) body is a request your API sends, so it reads like a response.
110
+
95
111
  ### Your own samples
96
112
 
97
113
  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 +127,21 @@ paths:
111
127
  planter list
112
128
  ```
113
129
 
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`.
130
+ These samples render as their own tabs, ahead of the generated ones, and stay as written when readers change the [Try it](#try-it-playground) form. The older `x-code-samples` spelling works too. To show only your own samples, set `codeSamples: false`. Unlike the generated samples, they're also in the operation page's Markdown copy, so `llms-full.txt`, the MCP server, and the assistant see your SDK calls.
131
+
132
+ A `source` can also be a `$ref` to a file that holds the sample's code, the way Redocly describes keeping samples in files of their own. Blume reads the file relative to the spec, fetching it when the spec is a URL. A `$ref` it can't read, or one that points inside a document (`#/…`), leaves that sample out with a [`BLUME_OPENAPI_CODE_SAMPLE_REF`](#spec-warnings) warning:
133
+
134
+ ```yaml openapi.yaml lineNumbers
135
+ paths:
136
+ /plants:
137
+ get:
138
+ x-codeSamples:
139
+ - lang: go
140
+ source:
141
+ $ref: ./code_samples/go/list_plants.go
142
+ ```
143
+
144
+ [Add SDK examples to generated API documentation](/guides/openapi-sdk-code-samples) walks through samples for a TypeScript and a Python SDK, with a check that catches samples an SDK release broke.
115
145
 
116
146
  ## Try it playground
117
147
 
@@ -148,9 +178,11 @@ reference: [
148
178
 
149
179
  The built-in proxy only forwards requests to the origins your specs declare in `servers` (at the document, path, or operation level, with variables at their defaults) — including across redirects — so a public docs deployment can't be aimed at other hosts on its network. A **Custom base URL** typed into the panel isn't a documented server: with the proxy enabled, requests to it are refused with a 403. It forwards only the headers the panel sets itself — the credentials and header parameters filled in, and the body's `Content-Type` — so cookies, credentials the browser attaches for the docs site (HTTP Basic auth on a password-protected preview, say), and headers your host adds (`X-Forwarded-For`, `CF-*`, `X-Vercel-*`) never reach the API. It reads a request body only up to 4 MB (anything larger gets a `413`), and every response it relays carries `Content-Security-Policy: sandbox`, `X-Content-Type-Options: nosniff`, and `Cross-Origin-Resource-Policy: same-origin` — plus `Content-Disposition: attachment` for HTML or SVG — so an API error page that echoes its input can't run script on the docs origin. The proxy is also [rate limited](/docs/configuration/rate-limiting) per reader, like every server route a reader can call.
150
180
 
181
+ When a Try it request is blocked, [Fix CORS errors in an API documentation playground](/guides/api-documentation-cors-errors) reproduces it, reads the preflight the browser sends, and fixes it with a CORS policy on your API or the built-in proxy.
182
+
151
183
  ## Multiple specs
152
184
 
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`:
185
+ 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
186
 
155
187
  ```ts blume.config.ts lineNumbers
156
188
  reference: [
@@ -165,6 +197,8 @@ reference: [
165
197
 
166
198
  `spec` is shorthand for a single-entry `sources`, so you only reach for `sources` when you have more than one. When two specs need different display options — a different code-sample set, say — list two `openapi()` adapters instead, each with its own `route`. An embedded [Scalar](/docs/references/scalar) reference beside native pages is its own `scalar()` adapter in the list. Sources are resolved in list order, and when two resolve to the same route the first one wins (the build warns about the one it dropped).
167
199
 
200
+ [Combine multiple OpenAPI specifications in one docs site](/guides/multiple-openapi-specs-documentation) builds an API portal this way, with each service's spec on its own route and tab.
201
+
168
202
  ### Per-source indexing
169
203
 
170
204
  Generated pages participate in search, `llms.txt`, and crawler indexing by default. A secondary or overlapping spec can opt out of any surface without hiding its pages or removing it from navigation:
@@ -233,6 +267,8 @@ Each action's `target` is a [JSONPath](https://www.rfc-editor.org/rfc/rfc9535) e
233
267
 
234
268
  Overlays apply to the spec as written, before Blume upgrades it to OpenAPI 3.1, so an overlay targets the version you wrote the spec in. Everything built from the spec sees the result: the operation pages, the sidebar, search, `llms.txt`, and the MCP server. With `sources`, give each source its own `overlays`. An overlay that doesn't apply fails the reference the way an unreadable spec does, so a broken overlay can't publish what it was meant to hide.
235
269
 
270
+ [Remove internal endpoints from public API docs](/guides/openapi-overlays-public-docs) walks through an overlay that strips internal operations from a generated spec on every build.
271
+
236
272
  ## Authorization
237
273
 
238
274
  Operations that declare [security requirements](https://spec.openapis.org/oas/v3.1.0#security-requirement-object) render an **Authorization** section above their parameters, and the generated code samples send a placeholder credential (`Authorization: Bearer YOUR_TOKEN`, an API-key header, or a query key — whatever the scheme calls for). There's nothing to configure: Blume reads `security` from the spec, so the reference always matches what the API actually enforces.
@@ -246,7 +282,7 @@ The OpenAPI semantics carry over as written:
246
282
 
247
283
  ## Webhooks and callbacks
248
284
 
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.
285
+ 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
286
 
251
287
  ```yaml openapi.yaml lineNumbers
252
288
  webhooks:
@@ -263,10 +299,30 @@ webhooks:
263
299
  description: Return a 200 to acknowledge the event.
264
300
  ```
265
301
 
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.
302
+ 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`.
303
+
304
+ 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
305
 
268
306
  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
307
 
308
+ [Write webhook documentation with OpenAPI 3.1](/guides/openapi-webhook-documentation) adds signature headers and acknowledgments to the webhook pages, plus a receiving guide with a working Node.js receiver.
309
+
310
+ ## Spec warnings
311
+
312
+ 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:
313
+
314
+ | Code | What's wrong |
315
+ | --- | --- |
316
+ | `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. |
317
+ | `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. |
318
+ | `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. |
319
+ | `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. |
320
+ | `BLUME_OPENAPI_CODE_SAMPLE_REF` | An `x-codeSamples` entry's `source` is a `$ref` Blume can't read, or one that points inside a document, so that [sample](#your-own-samples) is left out. |
321
+ | `BLUME_OPENAPI_X_WEBHOOKS` | A 3.1 or later spec keeps its webhooks under `x-webhooks`, the extension 3.0 tools read, and has no `webhooks`, so none of them render. Rename the field to `webhooks`. |
322
+ | `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. |
323
+
324
+ [Keep API documentation in sync with backend changes in CI](/guides/openapi-documentation-ci) runs those checks in GitHub Actions: it exports the spec from the backend, fails when the spec is stale, invalid, or breaking, and rebuilds the reference from it.
325
+
270
326
  ## Embedding Scalar instead
271
327
 
272
328
  `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.2",
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",
@@ -68,7 +68,7 @@ Every build publishes a generated agent skill for the site at `/skill.md` (`agen
68
68
  - **Components** — callouts, cards, steps, tabs, accordions, badges, file trees, and parameter tables, usable in MDX with no imports.
69
69
  - **Local search** — Orama in dev and production, with no hosted index; Pagefind, Algolia, and other backends are one adapter away (`search: pagefind()` from `blume/search`).
70
70
  - **AI** — `llms.txt`, raw Markdown URLs, a JSON docs API with an OpenAPI description, Copy as Markdown, Open in chat, an in-page assistant, and an MCP server endpoint served by the docs site itself.
71
- - **Narration** — a "Listen to this page" player (`narration: true` for browser voices, or `narration: { provider: gateway({ … }) }` from `blume/ai` for voices generated at build).
71
+ - **Narration** — a "Listen to this page" player (`narration: true` for browser voices, or `narration: { provider: gateway({ … }) }` from `blume/ai` for voices generated at build; `openai({ model, voice })` uses OpenAI's speech models instead, and with a `baseUrl` a self-hosted OpenAI-compatible speech server, and needs `@ai-sdk/openai` installed).
72
72
  - **Navigation** — inferred from files, refined with `meta.ts` or config.
73
73
  - **SEO** — metadata, Open Graph images, RSS feeds, and JSON-LD.
74
74
  - **Customization** — component overrides, React islands, custom pages, theme tokens, and a source-component registry via `blume add`.
@@ -32,7 +32,7 @@ Throughout this skill (including the `references/` files), **`<skill>` means the
32
32
  3. **Write `blume.config.ts`** with `defineConfig` from `blume`. Map only declared fields (see the reference's mapping table); rely on defaults everywhere else. A minimal result is `defineConfig({ title: "…" })`.
33
33
  4. **Restructure content.** Choose `content.root` (default `docs`) — **detect where `.md`/`.mdx` actually live, don't assume a `docs/` folder.** Many repos keep content directly under an app dir (`apps/docs/api/`, `.../getting-started/`) with no `docs/` subfolder; when so, set `content.root` to that dir and scope `content.include` to the real content folders rather than leaving a bare `content.root: "."` that scans everything (see `references/monorepo.md` §1). Order with numeric prefixes (`01-intro.mdx`), group without a URL segment via `(group)/` folders, and add a `meta.ts` (`defineMeta`) only where filesystem order isn't enough. **A source that already declares per-folder navigation in a sidecar file — Fumadocs `meta.json`, Nextra `_meta.*` — _is_ that case: convert each one to a `meta.ts`, carrying over its title/icon/order/collapse, rather than dropping it and hoping filenames reproduce the intent. Filesystem inference is the fallback for folders that declare no per-folder nav, never a reason to discard one that does.** Reach for an explicit `navigation.sidebar` only when the source nav genuinely can't be expressed by files. **Reshaping into folder-per-tab moves URLs** — track every old→new path as you go; you'll turn them into `redirects` in step 5.
34
34
  5. **Rewrite pages.** Map frontmatter to Blume's strict schema; convert callout JSX to `:::` directives — **directives (and math/mermaid/package-install fences) are MDX-only, so rename any `.md` page that needs them to `.mdx`**; rename components; turn snippets/partials into `<include>` statements or inline them (Blume has no import-based includes: `import Snippet from "…"` plus `<Snippet />` has to become one or the other); fix asset paths; **rewrite internal links** to their new routes (including OpenAPI/GraphQL operation links — see the API-reference sections, their slugs differ from most sources); **add a `redirects` entry for every route you moved** in step 4; **convert every icon name to Lucide** (Blume is Lucide-only — no FontAwesome/Tabler). Remove any duplicated H1 in the body (`title` renders the H1; bodies start at `##`). **If the source has a hand-maintained changelog and the repo is open source on GitHub, offer to swap it for the `githubReleases()` source** (see "Changelogs" below) rather than porting the entries. For **Mintlify**, run the bundled codemod first — `node <skill>/scripts/mintlify-codemod.mjs --write <content-dir>` deterministically remaps icons and drops/renames unsupported frontmatter keys, and reports the rest (unknown icons, OpenAPI-stub flags) for you to finish by hand (see `references/mintlify.md`).
35
- 6. **Adopt `package.json`.** Repoint `dev`/`build`/`start` → `blume dev`/`blume build`/`blume preview`, remove the old framework's deps, add `blume`. A config-only source (e.g. a bare Mintlify `docs.json`) has no manifest — scaffold one. **In a pnpm workspace:** if `pnpm-workspace.yaml`/`.npmrc` sets `minimumReleaseAge`, add **only** `blume` to `minimumReleaseAgeExclude` (don't disable the guard) so the just-published version installs. **Always regenerate the lockfile in the same change:** after editing deps run a plain `pnpm install` (from the workspace root) and commit `pnpm-lock.yaml` alongside `package.json` — CI/Vercel use `--frozen-lockfile`, so a stale lockfile fails the build before it starts. **If the repo uses (or the user wants) [Ultracite](https://www.ultracite.ai) for formatting:** its oxfmt formatter mangles the `:::` directives you just wrote unless you ship the bundled `assets/oxfmt@0.67.0.patch` and register it under `patchedDependencies` — see `references/monorepo.md` §6. See `references/monorepo.md` §2–3.
35
+ 6. **Adopt `package.json`.** Repoint `dev`/`build`/`start` → `blume dev`/`blume build`/`blume preview`, remove the old framework's deps, add `blume`. A config-only source (e.g. a bare Mintlify `docs.json`) has no manifest — scaffold one. **In a pnpm workspace:** if `pnpm-workspace.yaml`/`.npmrc` sets `minimumReleaseAge`, add **only** `blume` to `minimumReleaseAgeExclude` (don't disable the guard) so the just-published version installs. **Always regenerate the lockfile in the same change:** after editing deps run a plain `pnpm install` (from the workspace root) and commit `pnpm-lock.yaml` alongside `package.json` — CI/Vercel use `--frozen-lockfile`, so a stale lockfile fails the build before it starts. **If the repo uses (or the user wants) [Ultracite](https://www.ultracite.ai) for formatting:** its oxfmt formatter mangles the `:::` directives you just wrote unless you ship the bundled `assets/oxfmt@0.71.0.patch` and register it under `patchedDependencies` — see `references/monorepo.md` §6. See `references/monorepo.md` §2–3.
36
36
  7. **Wire up the host repo & deploy (non-trivial repos).** For a monorepo on Vercel, emit the root-aware install/build recipe and `apps/docs/vercel.json`, and tell the user the two settings you can't commit (Vercel Root Directory, Node 22). If the workspace pins Vite and `blume build` crashes inside Astro/Vite, apply the pnpm-patch workaround. All copy-pasteable in `references/monorepo.md` §4–5.
37
37
  8. **Verify.** Run `blume build` (frontmatter schema, duplicate routes, config — it fails on any error diagnostic by default; **never pass `--no-strict`**, which builds anyway and silently drops invalid pages) and `blume validate --strict` (internal links, heading anchors, assets — the link checker lives in `validate`, not `build`), fix diagnostics, then `blume dev` for a visual pass. End with a written summary of what was migrated, dropped, and approximated — **and every repo-specific edit you made** (pnpm-workspace, vercel.json, config globs) with the reason, plus any manual step left to the user (the Astro patch, Vercel dashboard settings).
38
38
 
@@ -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
 
@@ -1,12 +1,12 @@
1
- diff --git a/dist/markdown-BMigo7Hm.js b/dist/markdown-BMigo7Hm.js
2
- index bc9037f6c0de5516b139d8cdb195b1e25cd33bc0..a02c284e28bb535f9964a8a086ebb6549657e416 100644
3
- --- a/dist/markdown-BMigo7Hm.js
4
- +++ b/dist/markdown-BMigo7Hm.js
5
- @@ -4872,7 +4872,43 @@ function lu(e, t, r) {
1
+ diff --git a/dist/markdown-gizI1LFO.js b/dist/markdown-gizI1LFO.js
2
+ index 95a638402fa6f73b29ebb8d7a7ef229bee68a690..e2e2ea5b443fe10698c49c2ef3dc3d5909335915 100644
3
+ --- a/dist/markdown-gizI1LFO.js
4
+ +++ b/dist/markdown-gizI1LFO.js
5
+ @@ -4898,7 +4898,43 @@ function lu(e, t, r) {
6
6
  case "sentence": return Oh(e, r);
7
7
  case "word": return t.parser !== "mdx" ? zh(e, t) : Uh(e);
8
8
  case "whitespace": {
9
- - let { next: a } = e, u = a && /^>|^(?:[*+-]|#{1,6}|\d+[).])$/.test(a.value) && !NE(e) && !(t.proseWrap === "preserve" && RE(e)) ? "never" : t.proseWrap;
9
+ - let { next: a } = e, u = a && /^>|^(?:[*+-]|#{1,6}|\d+[).])$/.test(a.value) && !RE(e) && !(t.proseWrap === "preserve" && ME(e)) ? "never" : t.proseWrap;
10
10
  + let { next: a, previous: oxfmtFencePrev } = e;
11
11
  + // Preserve line breaks that sit directly against a `:::` container
12
12
  + // directive fence, so `proseWrap: "never"` keeps the opening/closing
@@ -43,7 +43,7 @@ index bc9037f6c0de5516b139d8cdb195b1e25cd33bc0..a02c284e28bb535f9964a8a086ebb654
43
43
  + }
44
44
  + oxfmtPrevIsTitledFence = oxfmtIsFence(oxfmtLineFirst);
45
45
  + }
46
- + let u = oxfmtIsFence(oxfmtFencePrev) || oxfmtPrevIsTitledFence || oxfmtIsFence(a) ? "preserve" : a && /^>|^(?:[*+-]|#{1,6}|\d+[).])$/.test(a.value) && !NE(e) && !(t.proseWrap === "preserve" && RE(e)) ? "never" : t.proseWrap;
46
+ + let u = oxfmtIsFence(oxfmtFencePrev) || oxfmtPrevIsTitledFence || oxfmtIsFence(a) ? "preserve" : a && /^>|^(?:[*+-]|#{1,6}|\d+[).])$/.test(a.value) && !RE(e) && !(t.proseWrap === "preserve" && ME(e)) ? "never" : t.proseWrap;
47
47
  return ou(e, n.value, u, !1, t);
48
48
  }
49
49
  case "emphasis": {
@@ -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.
@@ -190,13 +190,13 @@ This is a **manual step — do not fabricate the patch contents.** Leave the use
190
190
 
191
191
  If the repo uses (or the user wants to adopt) **[Ultracite](https://www.ultracite.ai)** for formatting — an `ultracite check` / `ultracite fix` script, an `ultracite` dev dep, or oxlint/oxfmt in the toolchain — it will format the migrated `.md`/`.mdx` **and break Blume's `:::` directives.** Ultracite's formatter is **oxfmt**, and under `proseWrap` oxfmt joins the opening/closing `:::` fence line into the surrounding prose, which invalidates the directive (`:::note` … `:::` callouts, titled fences like `:::warning[Heads up]`, tabs, steps — the exact syntax you convert callouts _into_ in step 5). Blume's own repo hits this and pins a patched oxfmt; a migrated repo needs the same patch or every directive silently degrades to literal text on the next `ultracite fix`.
192
192
 
193
- The fix is a committed **pnpm patch** (`patches/oxfmt@0.67.0.patch`), shipped with this skill at `assets/oxfmt@0.67.0.patch` — the same diff Blume's own repo applies, covering titled fences as well as bare ones. Unlike the Astro/Vite patch, this one is a **known, deterministic diff** — copy it in, don't regenerate it:
193
+ The fix is a committed **pnpm patch** (`patches/oxfmt@0.71.0.patch`), shipped with this skill at `assets/oxfmt@0.71.0.patch` — the same diff Blume's own repo applies, covering titled fences as well as bare ones. Unlike the Astro/Vite patch, this one is a **known, deterministic diff** — copy it in, don't regenerate it:
194
194
 
195
195
  ```bash
196
196
  # 1. Copy the shipped patch into the target repo's patches/ dir (keep the exact filename;
197
197
  # <skill> = this skill's directory, the one containing SKILL.md):
198
198
  mkdir -p patches
199
- cp "<skill>/assets/oxfmt@0.67.0.patch" patches/oxfmt@0.67.0.patch
199
+ cp "<skill>/assets/oxfmt@0.71.0.patch" patches/oxfmt@0.71.0.patch
200
200
  ```
201
201
 
202
202
  Then register it under `patchedDependencies` — `pnpm-workspace.yaml` on pnpm 10+, or root `package.json` (`pnpm.patchedDependencies`) on pnpm 9:
@@ -204,12 +204,12 @@ Then register it under `patchedDependencies` — `pnpm-workspace.yaml` on pnpm 1
204
204
  ```yaml
205
205
  # pnpm-workspace.yaml
206
206
  patchedDependencies:
207
- oxfmt@0.67.0: patches/oxfmt@0.67.0.patch
207
+ oxfmt@0.71.0: patches/oxfmt@0.71.0.patch
208
208
  ```
209
209
 
210
210
  Commit **both** the patch file and the `patchedDependencies` entry, then re-run `pnpm install`.
211
211
 
212
- - **The patch is pinned to `oxfmt@0.67.0`** (the version Blume's own repo pins). pnpm requires an exact version match, and the diff targets a file whose name is hashed per oxfmt release (`dist/markdown-*.js`), so it won't apply to any other version. If `pnpm why oxfmt` reports a different one, pin `oxfmt` to `0.67.0` as a direct dev dependency (Ultracite accepts any `oxfmt` ≥ 0.40 as a peer). If the repo can't take that pin, tell the user and fall back to keeping directive-heavy files out of the formatter's globs.
212
+ - **The patch is pinned to `oxfmt@0.71.0`** (the version Blume's own repo pins). pnpm requires an exact version match, and the diff targets a file whose name is hashed per oxfmt release (`dist/markdown-*.js`), so it won't apply to any other version. If `pnpm why oxfmt` reports a different one, pin `oxfmt` to `0.71.0` as a direct dev dependency (Ultracite accepts any `oxfmt` ≥ 0.59 as a peer). If the repo can't take that pin, tell the user and fall back to keeping directive-heavy files out of the formatter's globs.
213
213
  - **Not on pnpm?** The patch mechanism is pnpm-specific. For npm/yarn, either pin oxfmt and apply the diff with `patch-package`, or exclude `.md`/`.mdx` from Ultracite formatting so it never touches the directives — report whichever you chose.
214
214
 
215
215
  ---
@@ -221,5 +221,5 @@ Commit **both** the patch file and the `patchedDependencies` entry, then re-run
221
221
  - [ ] Ran plain `pnpm install`; committed `pnpm-lock.yaml` with the `package.json` change; verified `pnpm install --frozen-lockfile` is clean.
222
222
  - [ ] Wrote `apps/docs/vercel.json` + package scripts; told the user to set Root Directory + Node 22 in the Vercel project.
223
223
  - [ ] Checked for a workspace Vite override; if the build crashes inside Astro/Vite, gave the pnpm-patch recipe.
224
- - [ ] Uses Ultracite/oxfmt? Shipped `patches/oxfmt@0.67.0.patch` + registered it under `patchedDependencies` so formatting doesn't mangle `:::` directives.
224
+ - [ ] Uses Ultracite/oxfmt? Shipped `patches/oxfmt@0.71.0.patch` + registered it under `patchedDependencies` so formatting doesn't mangle `:::` directives.
225
225
  - [ ] Reported every repo-specific edit and every manual step left to the user.
@@ -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.