blume 2.0.3 → 2.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (607) hide show
  1. package/AGENTS.md +1 -0
  2. package/CHANGELOG.md +142 -0
  3. package/README.md +1 -1
  4. package/dist/cli/{chunk-5f86nr5m.js → chunk-073rdvgb.js} +21 -48
  5. package/dist/cli/{chunk-5f86nr5m.js.map → chunk-073rdvgb.js.map} +4 -5
  6. package/dist/cli/{chunk-ga0pf4aj.js → chunk-0n9xkp3d.js} +12 -3
  7. package/dist/cli/{chunk-ga0pf4aj.js.map → chunk-0n9xkp3d.js.map} +3 -3
  8. package/dist/cli/chunk-150jk7km.js +52 -0
  9. package/dist/cli/chunk-150jk7km.js.map +10 -0
  10. package/dist/cli/{chunk-9t7a85s3.js → chunk-313vnn40.js} +30 -19
  11. package/dist/cli/chunk-313vnn40.js.map +10 -0
  12. package/dist/cli/{chunk-tkacnehg.js → chunk-3v3ffbrj.js} +2 -2
  13. package/dist/cli/{chunk-q58y5e6a.js → chunk-3x6x3pph.js} +46 -24
  14. package/dist/cli/chunk-3x6x3pph.js.map +10 -0
  15. package/dist/cli/{chunk-1d7ve1dm.js → chunk-42t4jxdx.js} +37 -5
  16. package/dist/cli/chunk-42t4jxdx.js.map +12 -0
  17. package/dist/cli/{chunk-8ktnccpt.js → chunk-54ztw1nj.js} +21 -2
  18. package/dist/cli/chunk-54ztw1nj.js.map +10 -0
  19. package/dist/cli/{chunk-hm3vjy5s.js → chunk-5vjtgs6e.js} +28 -10
  20. package/dist/cli/{chunk-hm3vjy5s.js.map → chunk-5vjtgs6e.js.map} +4 -4
  21. package/dist/cli/chunk-66w9j7p6.js +322 -0
  22. package/dist/cli/chunk-66w9j7p6.js.map +14 -0
  23. package/dist/cli/{chunk-2hsdwb9n.js → chunk-7ppf1rva.js} +34 -18
  24. package/dist/cli/chunk-7ppf1rva.js.map +10 -0
  25. package/dist/cli/{chunk-r9rcc4w7.js → chunk-8y2kmjv8.js} +7 -7
  26. package/dist/cli/{chunk-r20tn01b.js → chunk-c1y538kd.js} +8 -2
  27. package/dist/cli/{chunk-r20tn01b.js.map → chunk-c1y538kd.js.map} +3 -3
  28. package/dist/cli/{chunk-2eytanqx.js → chunk-c887fj0s.js} +2 -2
  29. package/dist/cli/{chunk-p3v96n38.js → chunk-cfyg47zg.js} +16 -6
  30. package/dist/cli/chunk-cfyg47zg.js.map +10 -0
  31. package/dist/cli/chunk-cm89scdv.js +443 -0
  32. package/dist/cli/chunk-cm89scdv.js.map +11 -0
  33. package/dist/cli/{chunk-akbpwfxc.js → chunk-dzwehpy7.js} +2 -2
  34. package/dist/cli/{chunk-wrr3j9w9.js → chunk-e6atmn4h.js} +299 -72
  35. package/dist/cli/chunk-e6atmn4h.js.map +36 -0
  36. package/dist/cli/{chunk-tzmab476.js → chunk-e8xw9qkh.js} +8 -6
  37. package/dist/cli/{chunk-tzmab476.js.map → chunk-e8xw9qkh.js.map} +3 -3
  38. package/dist/cli/{chunk-6dsbexzp.js → chunk-e9q7a2ak.js} +17 -15
  39. package/dist/cli/{chunk-6dsbexzp.js.map → chunk-e9q7a2ak.js.map} +2 -2
  40. package/dist/cli/{chunk-pv29h0wf.js → chunk-ehweg4fr.js} +5155 -1249
  41. package/dist/cli/chunk-ehweg4fr.js.map +218 -0
  42. package/dist/cli/{chunk-c8chx29p.js → chunk-ep6nd0rn.js} +390 -318
  43. package/dist/cli/chunk-ep6nd0rn.js.map +36 -0
  44. package/dist/cli/{chunk-p73c0m7w.js → chunk-fdrkhpjy.js} +18 -15
  45. package/dist/cli/{chunk-p73c0m7w.js.map → chunk-fdrkhpjy.js.map} +3 -3
  46. package/dist/cli/{chunk-2z928egk.js → chunk-ft9vfys6.js} +5 -5
  47. package/dist/cli/{chunk-5m5nmvyq.js → chunk-gb420vvz.js} +316 -79
  48. package/dist/cli/chunk-gb420vvz.js.map +17 -0
  49. package/dist/cli/{chunk-vg9r4eb9.js → chunk-hebn0tpx.js} +86 -13
  50. package/dist/cli/chunk-hebn0tpx.js.map +14 -0
  51. package/dist/cli/{chunk-35d4wj9f.js → chunk-htrwgabr.js} +41 -23
  52. package/dist/cli/chunk-htrwgabr.js.map +15 -0
  53. package/dist/cli/{chunk-g4hq16wv.js → chunk-hx3xqnk9.js} +16 -14
  54. package/dist/cli/{chunk-g4hq16wv.js.map → chunk-hx3xqnk9.js.map} +2 -2
  55. package/dist/cli/chunk-j134vrp2.js +65 -0
  56. package/dist/cli/chunk-j134vrp2.js.map +10 -0
  57. package/dist/cli/{chunk-a58773jm.js → chunk-kma9f6sf.js} +2 -2
  58. package/dist/cli/chunk-kwrg63tm.js +31 -0
  59. package/dist/cli/chunk-kwrg63tm.js.map +10 -0
  60. package/dist/cli/{chunk-ey84smr6.js → chunk-mrcccgnh.js} +6 -6
  61. package/dist/cli/{chunk-ey84smr6.js.map → chunk-mrcccgnh.js.map} +1 -1
  62. package/dist/cli/{chunk-yfyb25rh.js → chunk-n0v7m8ry.js} +24 -8
  63. package/dist/cli/chunk-n0v7m8ry.js.map +14 -0
  64. package/dist/cli/{chunk-j85vccga.js → chunk-ncj7w596.js} +576 -367
  65. package/dist/cli/chunk-ncj7w596.js.map +35 -0
  66. package/dist/cli/{chunk-5xvm6tfj.js → chunk-nq1d20w2.js} +17 -15
  67. package/dist/cli/{chunk-5xvm6tfj.js.map → chunk-nq1d20w2.js.map} +2 -2
  68. package/dist/cli/{chunk-pehfxfta.js → chunk-nrns10mj.js} +6 -98
  69. package/dist/cli/chunk-nrns10mj.js.map +10 -0
  70. package/dist/cli/{chunk-crgn1q09.js → chunk-sds923fa.js} +4 -4
  71. package/dist/cli/chunk-t2m5j4pr.js +104 -0
  72. package/dist/cli/chunk-t2m5j4pr.js.map +11 -0
  73. package/dist/cli/{chunk-acanzt5p.js → chunk-tdqhyxfz.js} +6 -6
  74. package/dist/cli/chunk-tphac74f.js +126 -0
  75. package/dist/cli/chunk-tphac74f.js.map +11 -0
  76. package/dist/cli/chunk-w1r4vase.js +18 -0
  77. package/dist/cli/chunk-w1r4vase.js.map +10 -0
  78. package/dist/cli/{chunk-3em5wd2y.js → chunk-zccbjcj0.js} +4 -3
  79. package/dist/cli/{chunk-3em5wd2y.js.map → chunk-zccbjcj0.js.map} +2 -2
  80. package/dist/cli/index.js +31 -20
  81. package/dist/cli/index.js.map +5 -5
  82. package/dist/types/ai/agent-surface.d.ts +8 -7
  83. package/dist/types/ai/ai-catalog.d.ts +7 -0
  84. package/dist/types/ai/ask-context.d.ts +26 -0
  85. package/dist/types/ai/ask.d.ts +271 -58
  86. package/dist/types/ai/component-markdown.d.ts +2 -0
  87. package/dist/types/ai/index.d.ts +2 -2
  88. package/dist/types/ai/openapi-components.d.ts +4 -1
  89. package/dist/types/ai/site-skill.d.ts +26 -0
  90. package/dist/types/ai/skills.d.ts +16 -0
  91. package/dist/types/astro/integration.d.ts +9 -0
  92. package/dist/types/astro/markdown-negotiation.d.ts +3 -0
  93. package/dist/types/astro/pages.d.ts +10 -0
  94. package/dist/types/captcha/hcaptcha.d.ts +27 -0
  95. package/dist/types/captcha/index.d.ts +28 -0
  96. package/dist/types/captcha/schema.d.ts +29 -0
  97. package/dist/types/captcha/turnstile.d.ts +28 -0
  98. package/dist/types/cli/init/scaffold.d.ts +1 -1
  99. package/dist/types/components/content/api-field.d.ts +62 -0
  100. package/dist/types/components/content/api-page.d.ts +100 -0
  101. package/dist/types/components/layout/nav-utils.d.ts +26 -0
  102. package/dist/types/components/openapi/code-samples.d.ts +29 -0
  103. package/dist/types/components/openapi/helpers.d.ts +158 -0
  104. package/dist/types/components/openapi/security.d.ts +95 -0
  105. package/dist/types/components/openapi/snippets.d.ts +60 -0
  106. package/dist/types/consent/ethyca.d.ts +48 -0
  107. package/dist/types/consent/index.d.ts +37 -0
  108. package/dist/types/consent/native.d.ts +28 -0
  109. package/dist/types/consent/osano.d.ts +42 -0
  110. package/dist/types/consent/schema.d.ts +110 -0
  111. package/dist/types/core/changelog-index.d.ts +8 -0
  112. package/dist/types/core/config-input.d.ts +217 -23
  113. package/dist/types/core/data.d.ts +53 -4
  114. package/dist/types/core/footer.d.ts +8 -0
  115. package/dist/types/core/i18n-ui.d.ts +88 -0
  116. package/dist/types/core/includes.d.ts +8 -2
  117. package/dist/types/core/links.d.ts +8 -0
  118. package/dist/types/core/localizable.d.ts +7 -0
  119. package/dist/types/core/page-modes.d.ts +29 -0
  120. package/dist/types/core/probe.d.ts +2 -0
  121. package/dist/types/core/project-graph.d.ts +6 -0
  122. package/dist/types/core/redirect-patterns.d.ts +152 -0
  123. package/dist/types/core/related.d.ts +40 -0
  124. package/dist/types/core/schema.d.ts +670 -56
  125. package/dist/types/core/sources/cache.d.ts +3 -0
  126. package/dist/types/core/sources/contentful-rich-text.d.ts +11 -2
  127. package/dist/types/core/sources/contentful.d.ts +17 -1
  128. package/dist/types/core/sources/lexical.d.ts +5 -0
  129. package/dist/types/core/sources/normalize.d.ts +20 -1
  130. package/dist/types/core/sources/read.d.ts +5 -0
  131. package/dist/types/core/sources/remote.d.ts +37 -10
  132. package/dist/types/core/sources/sanity.d.ts +1 -1
  133. package/dist/types/core/types.d.ts +18 -1
  134. package/dist/types/core/variables.d.ts +42 -0
  135. package/dist/types/deploy/adapter-output.d.ts +2 -1
  136. package/dist/types/deploy/adapters/cloudflare.d.ts +7 -5
  137. package/dist/types/deploy/adapters/node.d.ts +58 -23
  138. package/dist/types/deploy/adapters/registry.d.ts +10 -0
  139. package/dist/types/deploy/adapters/types.d.ts +3 -2
  140. package/dist/types/deploy/node-headers.d.ts +13 -2
  141. package/dist/types/deploy/platforms/cloudflare.d.ts +15 -7
  142. package/dist/types/deploy/platforms/netlify.d.ts +7 -0
  143. package/dist/types/deploy/platforms/node.d.ts +3 -1
  144. package/dist/types/deploy/platforms/types.d.ts +18 -7
  145. package/dist/types/deploy/platforms/vercel.d.ts +26 -1
  146. package/dist/types/deploy/redirects.d.ts +55 -11
  147. package/dist/types/deploy/sitemap.d.ts +2 -1
  148. package/dist/types/deploy/vercel-negotiation.d.ts +51 -21
  149. package/dist/types/markdown/code-title.d.ts +17 -0
  150. package/dist/types/markdown/features.d.ts +3 -2
  151. package/dist/types/narration/build.d.ts +70 -0
  152. package/dist/types/narration/provider.d.ts +26 -0
  153. package/dist/types/narration/script.d.ts +154 -0
  154. package/dist/types/openapi/checks.d.ts +27 -0
  155. package/dist/types/openapi/graphql.d.ts +6 -0
  156. package/dist/types/openapi/model.d.ts +27 -10
  157. package/dist/types/openapi/overlay.d.ts +54 -0
  158. package/dist/types/openapi/parse.d.ts +16 -0
  159. package/dist/types/openapi/references.d.ts +30 -4
  160. package/dist/types/ratelimit/cloudflare.d.ts +39 -0
  161. package/dist/types/ratelimit/index.d.ts +27 -0
  162. package/dist/types/ratelimit/memory.d.ts +36 -0
  163. package/dist/types/ratelimit/schema.d.ts +119 -0
  164. package/dist/types/ratelimit/upstash.d.ts +46 -0
  165. package/dist/types/ratelimit/wrangler.d.ts +23 -0
  166. package/dist/types/reference/asyncapi.d.ts +10 -10
  167. package/dist/types/reference/graphql.d.ts +89 -11
  168. package/dist/types/reference/index.d.ts +1 -1
  169. package/dist/types/reference/openapi.d.ts +33 -12
  170. package/dist/types/reference/options.d.ts +82 -6
  171. package/dist/types/reference/scalar.d.ts +13 -0
  172. package/dist/types/reference/schema.d.ts +144 -36
  173. package/dist/types/search/adapters/registry.d.ts +3 -0
  174. package/dist/types/search/adapters/typesense.d.ts +14 -4
  175. package/dist/types/search/build.d.ts +3 -1
  176. package/dist/types/search/documents.d.ts +12 -1
  177. package/dist/types/search/orama-index.d.ts +13 -9
  178. package/dist/types/search/sync/algolia.d.ts +5 -2
  179. package/dist/types/search/sync/index.d.ts +3 -1
  180. package/dist/types/search/sync/typesense.d.ts +11 -7
  181. package/dist/types/seo/metatags.d.ts +20 -0
  182. package/dist/types/sources/contentful.d.ts +16 -0
  183. package/dist/types/sources/github-releases.d.ts +8 -1
  184. package/dist/types/sources/payload.d.ts +2 -0
  185. package/dist/types/sources/registry.d.ts +54 -0
  186. package/dist/types/sources/shared.d.ts +3 -0
  187. package/dist/types/sources/strapi.d.ts +2 -0
  188. package/docs/01-quickstart.mdx +4 -2
  189. package/docs/02-deployment.mdx +71 -17
  190. package/docs/03-upgrading.mdx +4 -4
  191. package/docs/04-migrating.mdx +1 -1
  192. package/docs/advanced/changelog.mdx +13 -2
  193. package/docs/advanced/skills.mdx +15 -1
  194. package/docs/cli/audit.mdx +51 -8
  195. package/docs/cli/doctor.mdx +5 -1
  196. package/docs/cli/evals.mdx +4 -4
  197. package/docs/cli/index.mdx +1 -0
  198. package/docs/cli/translate.mdx +1 -0
  199. package/docs/cli/validate.mdx +4 -3
  200. package/docs/configuration/analytics.mdx +3 -1
  201. package/docs/configuration/assistant.mdx +194 -38
  202. package/docs/configuration/consent.mdx +100 -0
  203. package/docs/configuration/customization.mdx +8 -6
  204. package/docs/configuration/export.mdx +5 -3
  205. package/docs/configuration/index.mdx +64 -5
  206. package/docs/configuration/meta.ts +3 -0
  207. package/docs/configuration/narration.mdx +119 -0
  208. package/docs/configuration/rate-limiting.mdx +90 -0
  209. package/docs/configuration/search.mdx +63 -12
  210. package/docs/configuration/theming.mdx +3 -1
  211. package/docs/content/components.mdx +108 -2
  212. package/docs/content/frontmatter.mdx +80 -2
  213. package/docs/content/i18n.mdx +39 -4
  214. package/docs/content/includes.mdx +16 -0
  215. package/docs/content/meta.mdx +1 -0
  216. package/docs/content/meta.ts +1 -0
  217. package/docs/content/navigation.mdx +32 -10
  218. package/docs/content/sources/contentful.mdx +35 -0
  219. package/docs/content/sources/custom.mdx +43 -0
  220. package/docs/content/sources/github-releases.mdx +39 -0
  221. package/docs/content/sources/index.mdx +91 -0
  222. package/docs/content/sources/meta.ts +16 -0
  223. package/docs/content/sources/notion.mdx +32 -0
  224. package/docs/content/sources/obsidian.mdx +37 -0
  225. package/docs/content/sources/payload.mdx +31 -0
  226. package/docs/content/sources/remote-mdx.mdx +44 -0
  227. package/docs/content/sources/sanity.mdx +29 -0
  228. package/docs/content/sources/strapi.mdx +32 -0
  229. package/docs/content/syntax.mdx +69 -4
  230. package/docs/content/variables.mdx +41 -0
  231. package/docs/discoverability/agent-discovery.mdx +29 -2
  232. package/docs/discoverability/index.mdx +2 -2
  233. package/docs/discoverability/json-api.mdx +2 -0
  234. package/docs/discoverability/llms-txt.mdx +2 -2
  235. package/docs/discoverability/markdown.mdx +3 -1
  236. package/docs/discoverability/mcp.mdx +4 -0
  237. package/docs/discoverability/metadata.mdx +17 -0
  238. package/docs/discoverability/sitemap-and-robots.mdx +1 -1
  239. package/docs/references/api-pages.mdx +74 -0
  240. package/docs/references/asyncapi.mdx +4 -2
  241. package/docs/references/graphql.mdx +14 -2
  242. package/docs/references/meta.ts +1 -1
  243. package/docs/references/openapi.mdx +148 -7
  244. package/docs/references/scalar.mdx +3 -1
  245. package/package.json +43 -8
  246. package/skills/blume/SKILL.md +3 -0
  247. package/skills/blume-migrate/SKILL.md +6 -6
  248. package/skills/blume-migrate/references/docusaurus.md +7 -7
  249. package/skills/blume-migrate/references/fumadocs.md +17 -13
  250. package/skills/blume-migrate/references/mintlify.md +37 -19
  251. package/skills/blume-migrate/references/nextra.md +10 -7
  252. package/skills/blume-migrate/references/starlight.md +11 -8
  253. package/skills/blume-migrate/scripts/mintlify-codemod.mjs +60 -14
  254. package/skills/blume-write-skill/SKILL.md +82 -0
  255. package/src/ai/agent-readability.ts +2 -1
  256. package/src/ai/agent-surface.ts +9 -7
  257. package/src/ai/ai-catalog.ts +6 -3
  258. package/src/ai/ask-context.ts +103 -20
  259. package/src/ai/ask-data.ts +6 -0
  260. package/src/ai/ask-limits.ts +13 -0
  261. package/src/ai/ask-tools.ts +142 -0
  262. package/src/ai/ask.ts +354 -76
  263. package/src/ai/changelog-markdown.ts +11 -6
  264. package/src/ai/component-markdown.ts +76 -1
  265. package/src/ai/cors.ts +14 -3
  266. package/src/ai/index.ts +12 -0
  267. package/src/ai/llms.ts +6 -2
  268. package/src/ai/markdown.ts +2 -1
  269. package/src/ai/mcp/data.ts +6 -0
  270. package/src/ai/mcp/query.ts +14 -6
  271. package/src/ai/openapi-components.ts +36 -6
  272. package/src/ai/relative-links.ts +4 -38
  273. package/src/ai/site-skill.ts +274 -0
  274. package/src/ai/skills.ts +35 -2
  275. package/src/ai/write-skill.ts +97 -0
  276. package/src/astro/generate.ts +210 -66
  277. package/src/astro/index.ts +1 -0
  278. package/src/astro/integration.ts +42 -4
  279. package/src/astro/markdown-negotiation.ts +3 -0
  280. package/src/astro/module-types.ts +9 -1
  281. package/src/astro/pages.ts +23 -0
  282. package/src/astro/templates.ts +406 -108
  283. package/src/astro/variables.ts +31 -0
  284. package/src/audit/catalog.ts +38 -3
  285. package/src/audit/checks/contrast.ts +183 -0
  286. package/src/audit/checks/i18n.ts +2 -3
  287. package/src/audit/checks/indexability.ts +2 -3
  288. package/src/audit/checks/links.ts +19 -13
  289. package/src/audit/checks/network.ts +110 -6
  290. package/src/audit/checks/redirects.ts +6 -4
  291. package/src/audit/checks/robots.ts +58 -2
  292. package/src/audit/checks/sitemap.ts +2 -3
  293. package/src/audit/redirects.ts +46 -15
  294. package/src/audit/run.ts +5 -0
  295. package/src/audit/types.ts +4 -1
  296. package/src/audit/url.ts +14 -5
  297. package/src/blume-modules.d.ts +12 -1
  298. package/src/captcha/client.ts +152 -0
  299. package/src/captcha/hcaptcha.ts +36 -0
  300. package/src/captcha/index.ts +28 -0
  301. package/src/captcha/schema.ts +32 -0
  302. package/src/captcha/turnstile.ts +38 -0
  303. package/src/captcha/verify.ts +71 -0
  304. package/src/cli/args.ts +51 -0
  305. package/src/cli/build-failure.ts +23 -0
  306. package/src/cli/command-meta.ts +5 -0
  307. package/src/cli/commands/audit.ts +10 -1
  308. package/src/cli/commands/dev.ts +18 -2
  309. package/src/cli/commands/doctor.ts +67 -28
  310. package/src/cli/commands/eval.ts +36 -4
  311. package/src/cli/commands/init.ts +6 -4
  312. package/src/cli/commands/skill.ts +96 -0
  313. package/src/cli/commands/validate.ts +10 -1
  314. package/src/cli/index.ts +5 -0
  315. package/src/cli/init/scaffold.ts +3 -3
  316. package/src/cli/prepare.ts +6 -3
  317. package/src/cli/required-secrets.ts +72 -30
  318. package/src/components/BlumePage.astro +2 -0
  319. package/src/components/content/ApiEndpoint.astro +18 -0
  320. package/src/components/content/ApiField.astro +79 -0
  321. package/src/components/content/ApiPlayground.astro +66 -0
  322. package/src/components/content/ApiRail.astro +19 -0
  323. package/src/components/content/Color.astro +1 -0
  324. package/src/components/content/FileTree.astro +1 -0
  325. package/src/components/content/GithubInfo.astro +1 -0
  326. package/src/components/content/Panel.astro +1 -0
  327. package/src/components/content/ParamField.astro +25 -0
  328. package/src/components/content/RequestExample.astro +17 -0
  329. package/src/components/content/ResponseExample.astro +17 -0
  330. package/src/components/content/ResponseField.astro +32 -0
  331. package/src/components/content/Step.astro +1 -0
  332. package/src/components/content/Tile.astro +1 -0
  333. package/src/components/content/Tooltip.astro +1 -0
  334. package/src/components/content/Tree.astro +1 -0
  335. package/src/components/content/TypeTable.astro +4 -1
  336. package/src/components/content/View.astro +21 -0
  337. package/src/components/content/ViewSwitcher.astro +168 -0
  338. package/src/components/content/api-field.ts +103 -0
  339. package/src/components/content/api-page.ts +248 -0
  340. package/src/components/content/mermaid-element.ts +15 -1
  341. package/src/components/content/view-switcher.ts +71 -0
  342. package/src/components/index.ts +2 -0
  343. package/src/components/islands/Assistant.astro +10 -1
  344. package/src/components/islands/assistant.tsx +104 -14
  345. package/src/components/islands/code-question.ts +62 -0
  346. package/src/components/islands/hooks.ts +136 -21
  347. package/src/components/islands/support-link.ts +68 -0
  348. package/src/components/layout/Analytics.astro +11 -2
  349. package/src/components/layout/Banner.astro +14 -10
  350. package/src/components/layout/Breadcrumbs.astro +6 -2
  351. package/src/components/layout/ConsentBanner.astro +62 -0
  352. package/src/components/layout/ConsentHead.astro +38 -0
  353. package/src/components/layout/Directory.astro +94 -0
  354. package/src/components/layout/DrawerSwitchers.astro +51 -0
  355. package/src/components/layout/Empty.astro +2 -2
  356. package/src/components/layout/Header.astro +122 -80
  357. package/src/components/layout/LanguageSwitcher.astro +48 -15
  358. package/src/components/layout/NarrationPlayer.astro +1085 -0
  359. package/src/components/layout/NavSelector.astro +33 -7
  360. package/src/components/layout/PageActions.astro +139 -24
  361. package/src/components/layout/PageFeedback.astro +54 -49
  362. package/src/components/layout/PageLayout.astro +71 -3
  363. package/src/components/layout/ReferenceLayout.astro +17 -6
  364. package/src/components/layout/RelatedPages.astro +42 -0
  365. package/src/components/layout/RootLayout.astro +318 -47
  366. package/src/components/layout/Search.astro +47 -19
  367. package/src/components/layout/SiteFooter.astro +124 -0
  368. package/src/components/layout/SiteMetatags.astro +16 -0
  369. package/src/components/layout/TableOfContents.astro +24 -17
  370. package/src/components/layout/consent/native.ts +79 -0
  371. package/src/components/layout/consent/types.ts +37 -0
  372. package/src/components/layout/footer-links.ts +53 -0
  373. package/src/components/layout/footer-socials.ts +87 -0
  374. package/src/components/layout/head-scripts.ts +6 -1
  375. package/src/components/layout/locale-redirect.ts +107 -0
  376. package/src/components/layout/nav-utils.ts +83 -0
  377. package/src/components/layout/page-feedback.ts +96 -0
  378. package/src/components/layout/search/algolia.ts +10 -1
  379. package/src/components/layout/search/analytics.ts +126 -0
  380. package/src/components/layout/search/endpoint.ts +48 -5
  381. package/src/components/layout/search/flexsearch.ts +17 -5
  382. package/src/components/layout/search/orama-cloud.ts +26 -14
  383. package/src/components/layout/search/pagefind.ts +114 -5
  384. package/src/components/layout/search/types.ts +32 -0
  385. package/src/components/layout/search/typesense.ts +19 -3
  386. package/src/components/openapi/AsyncApiOperation.astro +14 -7
  387. package/src/components/openapi/Authorization.astro +10 -0
  388. package/src/components/openapi/GraphqlOperation.astro +5 -0
  389. package/src/components/openapi/Operation.astro +184 -13
  390. package/src/components/openapi/ParametersTable.astro +8 -4
  391. package/src/components/openapi/Playground.astro +7 -1
  392. package/src/components/openapi/RequestBody.astro +24 -5
  393. package/src/components/openapi/RequestPanel.astro +59 -7
  394. package/src/components/openapi/Responses.astro +7 -4
  395. package/src/components/openapi/SchemaTable.astro +15 -3
  396. package/src/components/openapi/async-snippets.ts +65 -9
  397. package/src/components/openapi/async.ts +123 -29
  398. package/src/components/openapi/code-samples.ts +97 -0
  399. package/src/components/openapi/graphql-helpers.ts +35 -12
  400. package/src/components/openapi/helpers.ts +36 -2
  401. package/src/components/openapi/message-model.ts +54 -11
  402. package/src/components/openapi/message.ts +14 -4
  403. package/src/components/openapi/operation-model.ts +16 -7
  404. package/src/components/openapi/playground-client.ts +24 -5
  405. package/src/components/openapi/playground-schema.ts +1 -1
  406. package/src/components/openapi/schema-tree.ts +44 -14
  407. package/src/components/openapi/security.ts +16 -3
  408. package/src/components/openapi/snippets.ts +873 -10
  409. package/src/components/openapi/validate-json.ts +4 -3
  410. package/src/components/props.ts +6 -0
  411. package/src/consent/client.ts +95 -0
  412. package/src/consent/clients.ts +19 -0
  413. package/src/consent/ethyca.ts +71 -0
  414. package/src/consent/head.ts +24 -0
  415. package/src/consent/index.ts +37 -0
  416. package/src/consent/init.ts +29 -0
  417. package/src/consent/native.ts +36 -0
  418. package/src/consent/osano.ts +59 -0
  419. package/src/consent/schema.ts +30 -0
  420. package/src/core/builtin-tags.ts +5 -0
  421. package/src/core/changelog-index.ts +31 -1
  422. package/src/core/client-address.ts +19 -0
  423. package/src/core/component-diagnostics.ts +53 -0
  424. package/src/core/component-overrides.ts +32 -0
  425. package/src/core/config-input.ts +225 -23
  426. package/src/core/data.ts +46 -4
  427. package/src/core/diagnostics.ts +8 -2
  428. package/src/core/footer.ts +26 -0
  429. package/src/core/graph.ts +39 -36
  430. package/src/core/i18n-ui.ts +84 -2
  431. package/src/core/includes.ts +21 -4
  432. package/src/core/links.ts +63 -19
  433. package/src/core/localizable.ts +26 -0
  434. package/src/core/navigation.ts +43 -12
  435. package/src/core/page-modes.ts +56 -0
  436. package/src/core/probe.ts +26 -6
  437. package/src/core/project-graph.ts +163 -16
  438. package/src/core/redirect-patterns.ts +520 -0
  439. package/src/core/related.ts +131 -0
  440. package/src/core/schema.ts +339 -66
  441. package/src/core/sources/cache.ts +6 -0
  442. package/src/core/sources/contentful-rich-text.ts +29 -6
  443. package/src/core/sources/contentful.ts +135 -25
  444. package/src/core/sources/filesystem.ts +26 -4
  445. package/src/core/sources/github-releases.ts +12 -1
  446. package/src/core/sources/lexical.ts +15 -10
  447. package/src/core/sources/mdx-remote.ts +62 -10
  448. package/src/core/sources/normalize.ts +228 -41
  449. package/src/core/sources/notion.ts +7 -1
  450. package/src/core/sources/obsidian.ts +16 -3
  451. package/src/core/sources/payload.ts +38 -15
  452. package/src/core/sources/read.ts +8 -3
  453. package/src/core/sources/remote.ts +72 -15
  454. package/src/core/sources/resolve.ts +11 -1
  455. package/src/core/sources/sanity.ts +43 -25
  456. package/src/core/sources/strapi.ts +2 -2
  457. package/src/core/types.ts +19 -1
  458. package/src/core/ui-packs/ar.ts +44 -0
  459. package/src/core/ui-packs/bg.ts +46 -0
  460. package/src/core/ui-packs/bn.ts +44 -0
  461. package/src/core/ui-packs/ca.ts +47 -0
  462. package/src/core/ui-packs/cs.ts +45 -0
  463. package/src/core/ui-packs/da.ts +46 -0
  464. package/src/core/ui-packs/de.ts +47 -0
  465. package/src/core/ui-packs/el.ts +46 -0
  466. package/src/core/ui-packs/es.ts +47 -0
  467. package/src/core/ui-packs/fa.ts +46 -0
  468. package/src/core/ui-packs/fi.ts +46 -0
  469. package/src/core/ui-packs/fr.ts +47 -0
  470. package/src/core/ui-packs/he.ts +44 -0
  471. package/src/core/ui-packs/hi.ts +45 -0
  472. package/src/core/ui-packs/hr.ts +46 -0
  473. package/src/core/ui-packs/hu.ts +45 -0
  474. package/src/core/ui-packs/id.ts +46 -0
  475. package/src/core/ui-packs/it.ts +46 -0
  476. package/src/core/ui-packs/ja.ts +46 -0
  477. package/src/core/ui-packs/ko.ts +45 -0
  478. package/src/core/ui-packs/nl.ts +47 -0
  479. package/src/core/ui-packs/no.ts +45 -0
  480. package/src/core/ui-packs/pl.ts +46 -0
  481. package/src/core/ui-packs/pt-br.ts +47 -0
  482. package/src/core/ui-packs/pt.ts +46 -0
  483. package/src/core/ui-packs/ro.ts +46 -0
  484. package/src/core/ui-packs/ru.ts +46 -0
  485. package/src/core/ui-packs/sk.ts +45 -0
  486. package/src/core/ui-packs/sr.ts +46 -0
  487. package/src/core/ui-packs/sv.ts +45 -0
  488. package/src/core/ui-packs/th.ts +44 -0
  489. package/src/core/ui-packs/tr.ts +46 -0
  490. package/src/core/ui-packs/uk.ts +46 -0
  491. package/src/core/ui-packs/vi.ts +46 -0
  492. package/src/core/ui-packs/zh-tw.ts +44 -0
  493. package/src/core/ui-packs/zh.ts +44 -0
  494. package/src/core/variables.ts +103 -0
  495. package/src/deploy/adapter-output.ts +7 -2
  496. package/src/deploy/adapters/cloudflare.ts +7 -5
  497. package/src/deploy/adapters/node.ts +47 -9
  498. package/src/deploy/adapters/types.ts +7 -3
  499. package/src/deploy/artifacts.ts +79 -9
  500. package/src/deploy/cloudflare-negotiation.ts +35 -5
  501. package/src/deploy/headers.ts +2 -1
  502. package/src/deploy/node-headers.ts +63 -9
  503. package/src/deploy/platforms/cloudflare.ts +45 -9
  504. package/src/deploy/platforms/netlify.ts +35 -2
  505. package/src/deploy/platforms/node.ts +12 -2
  506. package/src/deploy/platforms/types.ts +21 -7
  507. package/src/deploy/platforms/vercel.ts +149 -17
  508. package/src/deploy/redirects.ts +151 -24
  509. package/src/deploy/rss.ts +3 -1
  510. package/src/deploy/sitemap.ts +39 -5
  511. package/src/deploy/vercel-negotiation.ts +326 -102
  512. package/src/eval/agents.ts +18 -1
  513. package/src/eval/prompts.ts +6 -3
  514. package/src/eval/report.ts +34 -13
  515. package/src/eval/run.ts +5 -0
  516. package/src/markdown/api-rail.ts +259 -0
  517. package/src/markdown/code-title.ts +35 -9
  518. package/src/markdown/features.ts +3 -2
  519. package/src/markdown/fence-meta.ts +2 -0
  520. package/src/markdown/include.ts +11 -1
  521. package/src/markdown/index.ts +28 -5
  522. package/src/markdown/math.ts +4 -4
  523. package/src/markdown/relative-links.ts +3 -2
  524. package/src/markdown/variables.ts +56 -0
  525. package/src/markdown/views.ts +82 -0
  526. package/src/narration/build.ts +412 -0
  527. package/src/narration/player.ts +249 -0
  528. package/src/narration/provider.ts +29 -0
  529. package/src/narration/script.ts +592 -0
  530. package/src/og/card.ts +7 -6
  531. package/src/openapi/checks.ts +273 -0
  532. package/src/openapi/graphql-build.ts +21 -0
  533. package/src/openapi/graphql.ts +11 -0
  534. package/src/openapi/model.ts +72 -27
  535. package/src/openapi/overlay.ts +256 -0
  536. package/src/openapi/parse.ts +182 -4
  537. package/src/openapi/references.ts +64 -8
  538. package/src/openapi/render-mdx.ts +5 -1
  539. package/src/openapi/scalar.ts +40 -6
  540. package/src/openapi/source.ts +40 -15
  541. package/src/ratelimit/cloudflare.ts +54 -0
  542. package/src/ratelimit/index.ts +30 -0
  543. package/src/ratelimit/memory.ts +45 -0
  544. package/src/ratelimit/runtime.ts +247 -0
  545. package/src/ratelimit/schema.ts +28 -0
  546. package/src/ratelimit/upstash.ts +60 -0
  547. package/src/ratelimit/wrangler.ts +70 -0
  548. package/src/reference/asyncapi.ts +2 -2
  549. package/src/reference/graphql.ts +20 -3
  550. package/src/reference/index.ts +1 -0
  551. package/src/reference/openapi.ts +21 -7
  552. package/src/reference/options.ts +80 -8
  553. package/src/reference/scalar.ts +33 -8
  554. package/src/registry/eject.ts +23 -5
  555. package/src/registry/registry.ts +7 -0
  556. package/src/search/adapters/locale-scope.ts +30 -0
  557. package/src/search/adapters/typesense.ts +14 -4
  558. package/src/search/build.ts +21 -3
  559. package/src/search/documents.ts +26 -2
  560. package/src/search/orama-index.ts +191 -34
  561. package/src/search/pagefind-ranking.ts +39 -0
  562. package/src/search/source-pages.ts +29 -0
  563. package/src/search/sync/algolia.ts +111 -10
  564. package/src/search/sync/index.ts +52 -13
  565. package/src/search/sync/orama-cloud.ts +3 -0
  566. package/src/search/sync/typesense.ts +76 -26
  567. package/src/seo/metatags.ts +67 -0
  568. package/src/sources/contentful.ts +12 -0
  569. package/src/sources/github-releases.ts +7 -1
  570. package/src/sources/shared.ts +3 -0
  571. package/src/theme/chrome-icons.ts +1 -0
  572. package/src/theme/color.ts +500 -0
  573. package/src/theme/entry.ts +85 -6
  574. package/src/theme/palette.ts +121 -45
  575. package/src/translate/prompts.ts +1 -0
  576. package/src/translate/validate.ts +31 -3
  577. package/src/upgrade/upgrade.ts +4 -171
  578. package/dist/cli/chunk-1d7ve1dm.js.map +0 -12
  579. package/dist/cli/chunk-2hsdwb9n.js.map +0 -10
  580. package/dist/cli/chunk-35d4wj9f.js.map +0 -15
  581. package/dist/cli/chunk-364znk6q.js +0 -75
  582. package/dist/cli/chunk-364znk6q.js.map +0 -11
  583. package/dist/cli/chunk-5m5nmvyq.js.map +0 -13
  584. package/dist/cli/chunk-8ktnccpt.js.map +0 -10
  585. package/dist/cli/chunk-9t7a85s3.js.map +0 -10
  586. package/dist/cli/chunk-b07cmahc.js +0 -145
  587. package/dist/cli/chunk-b07cmahc.js.map +0 -11
  588. package/dist/cli/chunk-c8chx29p.js.map +0 -36
  589. package/dist/cli/chunk-e04dxsz1.js +0 -39
  590. package/dist/cli/chunk-e04dxsz1.js.map +0 -10
  591. package/dist/cli/chunk-j85vccga.js.map +0 -36
  592. package/dist/cli/chunk-p3v96n38.js.map +0 -10
  593. package/dist/cli/chunk-pehfxfta.js.map +0 -10
  594. package/dist/cli/chunk-pv29h0wf.js.map +0 -190
  595. package/dist/cli/chunk-q58y5e6a.js.map +0 -10
  596. package/dist/cli/chunk-vg9r4eb9.js.map +0 -14
  597. package/dist/cli/chunk-wrr3j9w9.js.map +0 -35
  598. package/dist/cli/chunk-yfyb25rh.js.map +0 -14
  599. package/docs/content/sources.mdx +0 -334
  600. /package/dist/cli/{chunk-tkacnehg.js.map → chunk-3v3ffbrj.js.map} +0 -0
  601. /package/dist/cli/{chunk-r9rcc4w7.js.map → chunk-8y2kmjv8.js.map} +0 -0
  602. /package/dist/cli/{chunk-2eytanqx.js.map → chunk-c887fj0s.js.map} +0 -0
  603. /package/dist/cli/{chunk-akbpwfxc.js.map → chunk-dzwehpy7.js.map} +0 -0
  604. /package/dist/cli/{chunk-2z928egk.js.map → chunk-ft9vfys6.js.map} +0 -0
  605. /package/dist/cli/{chunk-a58773jm.js.map → chunk-kma9f6sf.js.map} +0 -0
  606. /package/dist/cli/{chunk-crgn1q09.js.map → chunk-sds923fa.js.map} +0 -0
  607. /package/dist/cli/{chunk-acanzt5p.js.map → chunk-tdqhyxfz.js.map} +0 -0
@@ -68,6 +68,8 @@ export default defineConfig({
68
68
  llmsTxt: true,
69
69
  // AI Catalog / ARD manifest at /.well-known/ai-catalog.json (needs deployment.site)
70
70
  catalog: true,
71
+ // Your site's agent skill at /skill.md (needs deployment.site)
72
+ skillMd: true,
71
73
  // MCP server (needs server output)
72
74
  mcp: {
73
75
  enabled: false,
@@ -99,6 +101,7 @@ export default defineConfig({
99
101
  | `description` | — | Default meta description, used for SEO and OG. |
100
102
  | `logo` | — | Brand mark and/or wordmark shown in the header. |
101
103
  | `banner` | — | Site-wide announcement bar above the header. |
104
+ | `footer` | — | A row of links and social profile icons below the content. |
102
105
 
103
106
  ### Logo
104
107
 
@@ -185,6 +188,42 @@ banner: {
185
188
 
186
189
  When `dismissible` is on, the bar shows a close button and stays hidden for that visitor afterward. The dismissal key defaults to the content text, so editing the message brings the banner back; set a stable `id` to keep it dismissed across edits.
187
190
 
191
+ On a [multilingual](/docs/content/i18n) site, `content` and the link's `text` also take a map of locale code to text. Each page shows its own locale's entry, falling back to the default locale's, and the link moves into the reader's locale whenever that locale serves the page:
192
+
193
+ ```ts blume.config.ts lineNumbers
194
+ banner: {
195
+ content: { en: "Version 2.0 is here!", fr: "La version 2.0 est là !" },
196
+ link: {
197
+ text: { en: "Read the release notes", fr: "Lire les notes de version" },
198
+ href: "/changelog",
199
+ },
200
+ },
201
+ ```
202
+
203
+ A translated banner keys dismissal on the default locale's text, so one dismissal hides it in every language.
204
+
205
+ ### Footer
206
+
207
+ Every page ends with a footer: one row, as tall as the header, with your links on one side and social profile icons on the other. The footer is the only place Blume links your social profiles and repository, so the [`github`](#github) repository comes first among the icons whenever it's set. `footer` adds the rest:
208
+
209
+ ```ts blume.config.ts lineNumbers
210
+ footer: {
211
+ links: [
212
+ { label: "Pricing", href: "https://acme.dev/pricing" },
213
+ { label: "Changelog", href: "/changelog" },
214
+ { label: "Blog", href: "https://acme.dev/blog" },
215
+ ],
216
+ socials: {
217
+ x: "https://x.com/acme",
218
+ discord: "https://discord.gg/acme",
219
+ },
220
+ },
221
+ ```
222
+
223
+ `links` appear in the order you write them. Write internal links as if mounted at root; an external link opens in a new tab. On a [multilingual](/docs/content/i18n#per-locale-navigation) site, a `label` can be a map of locale code to text, and an internal link moves into the reader's locale whenever that locale serves the page. `socials` maps a platform to its profile URL, and the icons appear in the order you write them. The platforms are `bluesky`, `discord`, `facebook`, `github`, `hacker-news`, `instagram`, `linkedin`, `medium`, `podcast`, `reddit`, `slack`, `telegram`, `threads`, `website`, `x`, and `youtube`. A `github` social replaces the repository link; hide that link with [`navigation.repo`](/docs/content/navigation#repository-link).
224
+
225
+ A site with no links, profiles, or repository has no footer. Custom pages built on `PageLayout` show it too, unless they fill the layout's own `footer` slot. To replace it entirely, override the [`Footer` layout slot](/docs/configuration/customization#layout-slots).
226
+
188
227
  ## Content
189
228
 
190
229
  Where your content lives and how Blume discovers it. See [Pages](/docs/content) for how files become routes.
@@ -272,7 +311,7 @@ A key can be declared site-wide or per-type, not both. See [Per-type keys](/docs
272
311
 
273
312
  ## GitHub
274
313
 
275
- Point Blume at your repository with `github`. It powers the header [repository link](/docs/content/navigation#repository-link) and the **Edit on GitHub** [page action](/docs/content/navigation#page-actions):
314
+ Point Blume at your repository with `github`. It powers the footer's [repository link](/docs/content/navigation#repository-link) and the **Edit on GitHub** [page action](/docs/content/navigation#page-actions):
276
315
 
277
316
  ```ts blume.config.ts lineNumbers
278
317
  github: {
@@ -420,7 +459,21 @@ export default defineConfig({
420
459
  });
421
460
  ```
422
461
 
423
- A reader's answer is sent as a `feedback` [custom event](/docs/configuration/analytics#custom-events) — with `helpful` (`"yes"` or `"no"`), `path`, and `title` — through every configured analytics adapter that has an event API, and as a `blume:track` event on `window`. Without an analytics adapter, the answer isn't recorded anywhere. The question and thank-you text are UI strings you can translate with [`i18n.ui`](/docs/content/i18n#translated-ui).
462
+ A reader's answer is sent as a `feedback` [custom event](/docs/configuration/analytics#custom-events) — with `helpful` (`"yes"` or `"no"`), `path`, and `title` — through every configured analytics adapter that has an event API, and as a `blume:track` event on `window`. Fathom, Microsoft Clarity, and Hotjar take an event's name alone, without its properties, so they count ratings but can't tell a yes from a no. Without an analytics adapter, the answer isn't recorded anywhere, and with [cookie consent](/docs/configuration/consent) on, the rating only shows for readers who allowed analytics, since from anyone else an answer would go nowhere. The question and thank-you text are UI strings you can translate with [`i18n.ui`](/docs/content/i18n#translated-ui).
463
+
464
+ ### Written feedback
465
+
466
+ To hear more than yes or no, turn on comments. After rating a page, the reader gets a box to say what worked or what's missing:
467
+
468
+ ```ts blume.config.ts
469
+ export default defineConfig({
470
+ feedback: { comments: true },
471
+ });
472
+ ```
473
+
474
+ A comment is sent as a `feedback_comment` event with the same `helpful`, `path`, and `title` as the rating, plus the reader's `comment` (up to 1,000 characters). It goes through the same adapters as the rating, so read comments where you read events. Providers cap how long a property value can be, so a long comment may not arrive whole: Google Analytics keeps 100 characters, Mixpanel 255 bytes (fewer characters in a script that takes several bytes a character, like Japanese), and Vercel Web Analytics takes values up to 255 characters, while PostHog and Segment pass comments whole. Fathom, Clarity, and Hotjar get no comment text at all. Plausible groups identical values rather than listing each comment, so it suits ratings better than comments. Readers can write anything in the box, so treat comments as they might contain personal details.
475
+
476
+ The box's label and button are UI strings too (`feedback.comment` and `feedback.send`).
424
477
 
425
478
  ## Feature options
426
479
 
@@ -432,11 +485,15 @@ Each of these has its own guide. The config field is the entry point:
432
485
  | `navigation` | Explicit sidebar and header tabs | [Navigation](/docs/content/navigation) |
433
486
  | `search` | Provider (Orama, Pagefind, Algolia, and more) and indexing | [Search](/docs/configuration/search) |
434
487
  | `markdown` | Markdown rendering options — code blocks, heading anchors, image zoom | [Syntax](/docs/content/syntax) |
435
- | `agents` | `llms.txt`, Markdown mirrors, the JSON API, the hosted MCP server, skills, and discovery manifests for coding agents | [SEO and AEO](/docs/discoverability) |
488
+ | `agents` | `llms.txt`, Markdown mirrors, the JSON API, the hosted MCP server, skills, and discovery manifests for coding agents | [SEO and GEO](/docs/discoverability) |
436
489
  | `ai` | The in-page assistant and the Open in chat action | [Assistant](/docs/configuration/assistant) |
490
+ | `rateLimit` | How often one reader can call the assistant, the playground proxy, and server-side search, with an adapter from `blume/ratelimit` (on by default) | [Rate limiting](/docs/configuration/rate-limiting) |
491
+ | `narration` | The "Listen to this page" player, with browser voices or voices generated at build | [Narration](/docs/configuration/narration) |
437
492
  | `reference` | API references: `openapi()`, `asyncapi()`, `graphql()`, and `scalar()` adapters from `blume/reference` | [OpenAPI](/docs/references/openapi), [AsyncAPI](/docs/references/asyncapi), [GraphQL](/docs/references/graphql), [Scalar](/docs/references/scalar) |
493
+ | `api` | The server, auth, and Try it panel of hand-written endpoint pages | [Hand-written API pages](/docs/references/api-pages) |
438
494
  | `analytics` | Adapters from `blume/analytics` — PostHog, Google Analytics, Plausible, Mixpanel, Segment, and more — plus custom scripts | [Analytics](/docs/configuration/analytics) |
439
- | `seo` | Metadata, OG images, feeds, structured data, sitemap, robots | [SEO and AEO](/docs/discoverability) |
495
+ | `consent` | A cookie consent adapter from `blume/consent` — Blume's own banner, Osano, or Ethyca — that holds analytics until the reader allows it | [Cookie consent](/docs/configuration/consent) |
496
+ | `seo` | Metadata, OG images, feeds, structured data, sitemap, robots | [SEO and GEO](/docs/discoverability) |
440
497
  | `deployment` | A host adapter from `blume/deploy`, or `{ site, base }` for a static build | [Deployment](/docs/deployment) |
441
498
  | `redirects` | Permanent and temporary redirects | [Deployment](/docs/deployment#redirects) |
442
499
  | `integrations` | Astro integrations appended after Blume's built-ins | [Customization](/docs/configuration/customization#astro-integrations) |
@@ -446,7 +503,9 @@ Each of these has its own guide. The config field is the entry point:
446
503
  | `export` | Reader-facing PDF and EPUB downloads | [Export](/docs/configuration/export) |
447
504
  | `examples` | Where `<Component path>` example previews live, and the CSS injected into their frames | [Component](/docs/content/components#component) |
448
505
  | `react` | React island behavior — the React Compiler's auto-memoization | [Islands](/docs/content/islands#frameworks) |
449
- | `feedback` | The "Was this page helpful?" rating at the foot of each page (default `true`) | [Page feedback](#page-feedback) |
506
+ | `variables` | Values `{{name}}` reads in any page | [Variables](/docs/content/variables) |
507
+ | `feedback` | The "Was this page helpful?" rating at the foot of each page (default `true`), and optional written comments after it | [Page feedback](#page-feedback) |
508
+ | `changelog` | The title and description of the generated `/changelog` index, each a string or a per-locale map | [Changelogs](/docs/advanced/changelog#the-index-page) |
450
509
 
451
510
  ## Precedence
452
511
 
@@ -7,7 +7,10 @@ export default defineMeta({
7
7
  "customization",
8
8
  "search",
9
9
  "assistant",
10
+ "rate-limiting",
11
+ "narration",
10
12
  "analytics",
13
+ "consent",
11
14
  "export",
12
15
  ],
13
16
  title: "Configuration",
@@ -0,0 +1,119 @@
1
+ ---
2
+ title: Narration
3
+ description: A "Listen to this page" player that reads each page aloud and highlights the sentence being read, using the reader's own browser voices or neural voices generated at build time.
4
+ ---
5
+
6
+ Narration adds a **Listen to this page** player under each page's description. Readers press play, and the page is read aloud from the title down while the sentence being read is highlighted and kept in view. It's opt-in:
7
+
8
+ ```ts blume.config.ts lineNumbers
9
+ export default defineConfig({
10
+ narration: true,
11
+ });
12
+ ```
13
+
14
+ With `true`, pages are read with the reader's own [browser voices](#browser-voices): no key, no build step, and it works on any host, static or not. Add a `provider` to use [generated voices](#generated-voices) instead.
15
+
16
+ ## What it reads
17
+
18
+ Narration reads the page title, the description, headings, paragraphs, list items, and card text, in order. Some components are introduced with a short spoken cue so the listener knows what kind of content comes next:
19
+
20
+ | Component | Cue |
21
+ | --- | --- |
22
+ | [Callouts](/docs/content/syntax#callouts) | "Note.", "Tip.", "Warning.", and so on |
23
+ | [Steps](/docs/content/components#steps) | "Step 1.", "Step 2." |
24
+ | [Tabs](/docs/content/components#tabs) | "macOS tab." Every tab is read, not just the open one |
25
+ | [Accordions](/docs/content/components#accordion) and [Expandable](/docs/content/components#expandable) | "Expandable section." |
26
+
27
+ When narration reaches a closed section or a tab that isn't showing, it opens it, so the highlight always lands on text the reader can see.
28
+
29
+ Code blocks, tables, images, video, diagrams, math, type tables, file trees, and live component previews are skipped. They don't make sense read aloud.
30
+
31
+ The player only appears on pages with enough prose to be worth listening to, roughly 50 words. Very short pages, and pages that are mostly code or API fields, don't get one.
32
+
33
+ ## Listening
34
+
35
+ While a page plays, the player stays pinned under the header with play and pause, previous and next sentence, a progress slider, the time read against the page's length, and a speed control from 0.8× to 2×. The speed is remembered for the next page.
36
+
37
+ The page scrolls to keep the sentence being read in view. If the reader scrolls away, following stops and the audio carries on; scrolling back to the sentence, or pressing **Follow along**, picks it up again. Opening another page stops the narration.
38
+
39
+ ## Browser voices
40
+
41
+ `narration: true` reads the page with the [Web Speech API](https://developer.mozilla.org/docs/Web/API/SpeechSynthesis) and the voices on the reader's device, picking one for the page's language. The player stays hidden on a browser with no voice for that language rather than reading the page in the wrong accent.
42
+
43
+ This needs nothing from you and costs nothing, but how it sounds depends on the device: the system voices on macOS, iOS, and Windows are good, while some Linux browsers ship few or none.
44
+
45
+ ## Generated voices
46
+
47
+ Pass a `provider` to generate audio with a neural speech model at build time. The provider is the same [`gateway()`](/docs/configuration/assistant#adapters) adapter the assistant uses, pointed at a speech model:
48
+
49
+ ```ts blume.config.ts lineNumbers
50
+ import { defineConfig } from "blume";
51
+ import { gateway } from "blume/ai";
52
+
53
+ export default defineConfig({
54
+ narration: {
55
+ provider: gateway({
56
+ model: "openai/tts-1-hd",
57
+ voice: "alloy",
58
+ }),
59
+ },
60
+ });
61
+ ```
62
+
63
+ `blume build` reads each built page exactly as the player will, splits it into sentences, and generates one clip per sentence through the [Vercel AI Gateway](https://vercel.com/docs/ai-gateway). The clips and a small manifest per page are written into your build as static files under `/blume-narration/`, so replays cost nothing and nothing runs on a server.
64
+
65
+ Before it generates anything, the build logs how many new clips it needs and how many characters they cover, so the cost is visible up front:
66
+
67
+ ```txt
68
+ Generating narration: 214 new clip(s), 15,880 characters, with openai/tts-1-hd
69
+ ```
70
+
71
+ | Option | Default | Description |
72
+ | --- | --- | --- |
73
+ | `model` | `openai/tts-1-hd` | A gateway speech model: `openai/tts-1`, `openai/tts-1-hd`, `fish-audio/s2.1-pro`, `spacexai/grok-tts`, and others the gateway lists. |
74
+ | `voice` | `alloy` | The model's voice. |
75
+ | `instructions` | | How the voice should sound, for models that take instructions ("Read calmly, like a teacher"). |
76
+ | `apiKeyEnv` | `AI_GATEWAY_API_KEY` | The env var holding the gateway key. On Vercel, the build's OIDC token works too. |
77
+ | `headers` | | Static headers sent with every request. |
78
+ | `providerOptions` | | Passed to the AI SDK's `generateSpeech` as-is, for model settings Blume doesn't name. |
79
+
80
+ ### Caching
81
+
82
+ Clips are cached in `node_modules/.cache/blume/narration` by what decides their sound: the sentence, its language, the model, the voice, and the instructions. A rebuild only pays for sentences that changed, and a sentence shared by many pages is generated once. Vercel and Netlify restore `node_modules` from their build caches, so a deploy there regenerates only what changed. On other CI, cache that directory between runs.
83
+
84
+ ### Fallbacks
85
+
86
+ Generated voices fall back to browser voices wherever no clips exist:
87
+
88
+ - In `blume dev`, which never generates audio. Run `blume build` and `blume preview` to hear the generated voice locally.
89
+ - When the key isn't set at build time. The build warns and skips generation.
90
+ - When generation fails. The build stops at the first failed clip, since the AI SDK has already retried it, and keeps every clip it has cached.
91
+
92
+ ## Languages
93
+
94
+ Narration reads each page in its content language, so on an [internationalized](/docs/content/i18n) site every locale is read in its own voice. The spoken cues and the player's labels are translated in every built-in UI language. Override them per locale under `narration` in [`i18n.ui`](/docs/content/i18n#translated-ui).
95
+
96
+ ## Turning it off for a page
97
+
98
+ Set `narration: false` in a page's [frontmatter](/docs/content/frontmatter) to leave that page without a player:
99
+
100
+ ```yaml
101
+ ---
102
+ title: Changelog
103
+ narration: false
104
+ ---
105
+ ```
106
+
107
+ ## Keeping content out
108
+
109
+ Add `data-blume-narration="skip"` to an element to leave it, and everything inside it, out of narration. This is how Blume skips its own type tables and previews, and it works the same on your own components and islands:
110
+
111
+ ```html
112
+ <div data-blume-narration="skip">
113
+ <PricingCalculator />
114
+ </div>
115
+ ```
116
+
117
+ ## Analytics
118
+
119
+ The player sends a `narration_play` event, with the `engine` (`audio` or `browser`) and the page's `path`, when a reader starts listening, and `narration_complete` when a page plays to the end. Both go through your [analytics](/docs/configuration/analytics#custom-events) adapters like the other custom events.
@@ -0,0 +1,90 @@
1
+ ---
2
+ title: Rate limiting
3
+ description: Limit how often one reader can call the assistant, the API playground proxy, and server-side search, on by default, with the count kept in memory, Upstash, or Cloudflare.
4
+ ---
5
+
6
+ The server routes a reader can call cost you something each time: the [assistant](/docs/configuration/assistant) spends model tokens, the [API playground](/docs/references/openapi#cors-and-the-proxy) proxy relays requests to your API, and [Mixedbread search](/docs/configuration/search#mixedbread) runs a paid query. Blume limits how often one reader, identified by IP address, can call each of them. A reader over the limit gets `429 Too Many Requests` with a `Retry-After` header, and the assistant tells them to try again in a few minutes.
7
+
8
+ Rate limiting is on by default, at 30 requests per reader per route every 10 minutes: well above what a person sends to the assistant or the playground, and low enough to stop a script. Search counts differently. The search dialog sends a query each time the reader pauses while typing, so one search can take several requests, and a reader who searches steadily for a few minutes can reach the limit. If your site uses Mixedbread search, raise `requests`, which applies to every route. Rate limiting only matters for a server build; a static site has no server routes to limit.
9
+
10
+ ```ts blume.config.ts lineNumbers
11
+ import { defineConfig } from "blume";
12
+ import { upstash } from "blume/ratelimit";
13
+
14
+ export default defineConfig({
15
+ rateLimit: upstash({ requests: 30, window: 600 }), // window in seconds
16
+ });
17
+ ```
18
+
19
+ Each route keeps its own count, so a busy playground session doesn't use up the reader's assistant questions.
20
+
21
+ ## Where the count lives
22
+
23
+ | Adapter | Count kept in | Limit holds |
24
+ | --- | --- | --- |
25
+ | `memory()` | The server's memory (the default) | Exactly on one server; per instance on serverless hosts |
26
+ | `upstash()` | [Upstash Redis](https://upstash.com) | Exactly, on every host |
27
+ | `cloudflare()` | Cloudflare's Workers rate limiting | Per Cloudflare location |
28
+
29
+ ### Memory
30
+
31
+ `memory()` needs nothing. On a server that runs as one process, like `deployment: node()`, the count is exact. Vercel, Netlify, and Cloudflare run many short-lived instances, and each keeps its own count, so there it stops a burst from one reader rather than enforcing the limit exactly. For an exact limit on those hosts, use one of the shared stores below.
32
+
33
+ ```ts blume.config.ts lineNumbers
34
+ import { memory } from "blume/ratelimit";
35
+
36
+ rateLimit: memory({ requests: 60, window: 600 }),
37
+ ```
38
+
39
+ ### Upstash
40
+
41
+ `upstash()` keeps the count in Upstash Redis, which every instance shares. Create a database, then set its REST endpoint and token as `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN`. Blume talks to Upstash's REST API, so there's nothing to install. Until both are set, the routes count in memory and `blume build` warns.
42
+
43
+ ```ts blume.config.ts lineNumbers
44
+ import { upstash } from "blume/ratelimit";
45
+
46
+ rateLimit: upstash(),
47
+ ```
48
+
49
+ A database added from Vercel's Marketplace sets its endpoint and token as `KV_REST_API_URL` and `KV_REST_API_TOKEN` instead, with any prefix you chose when you connected it. Name the variables with `urlEnv` and `tokenEnv`:
50
+
51
+ ```ts blume.config.ts lineNumbers
52
+ rateLimit: upstash({ urlEnv: "KV_REST_API_URL", tokenEnv: "KV_REST_API_TOKEN" }),
53
+ ```
54
+
55
+ ### Cloudflare
56
+
57
+ `cloudflare()` counts with [Workers rate limiting](https://developers.cloudflare.com/workers/runtime-apis/bindings/rate-limit/), for a site deployed with `deployment: cloudflare()`. Blume declares the binding in the Worker's config when it builds, so there's nothing to set up. Cloudflare counts per location and takes a window of 10 or 60 seconds, so its defaults are 10 requests per 60 seconds.
58
+
59
+ ```ts blume.config.ts lineNumbers
60
+ import { cloudflare } from "blume/deploy";
61
+ import { cloudflare as cloudflareRateLimit } from "blume/ratelimit";
62
+
63
+ export default defineConfig({
64
+ deployment: cloudflare(),
65
+ rateLimit: cloudflareRateLimit({ requests: 10, window: 60 }),
66
+ });
67
+ ```
68
+
69
+ The binding's namespace comes from the Worker's name, so two sites on one account don't share a count. Set `namespaceId` to choose it yourself.
70
+
71
+ ## Behind a reverse proxy
72
+
73
+ Vercel, Netlify, and Cloudflare tell Blume each reader's address. A `node()` server behind a reverse proxy, like nginx or Caddy, sees every request come from the proxy, so every reader shares one count and a busy site hits the limit for everyone. List the host the proxy answers for in `allowedDomains`:
74
+
75
+ ```ts blume.config.ts lineNumbers
76
+ import { node } from "blume/deploy";
77
+
78
+ deployment: node({
79
+ site: "https://docs.example.com",
80
+ allowedDomains: [{ hostname: "docs.example.com" }],
81
+ }),
82
+ ```
83
+
84
+ This is Astro's [`security.allowedDomains`](https://docs.astro.build/en/reference/configuration-reference/#securityalloweddomains): for a request whose `Host` or `X-Forwarded-Host` header matches an entry, Astro takes the reader's address from the first entry of `X-Forwarded-For`. Have the proxy pass the site's host through and replace any `X-Forwarded-For` the reader sent with the address it sees, like nginx's `proxy_set_header X-Forwarded-For $remote_addr;`. A proxy that appends to the header instead lets a reader pick their own address and get a fresh count with every request. Leave `allowedDomains` unset when readers reach the server directly.
85
+
86
+ ## Turning it off
87
+
88
+ Set `rateLimit: false` to let every request through, when your host's firewall already limits these routes, say. A request whose address the host can't tell is always let through, and so is one a shared store fails to count: the failure is logged, and readers never get locked out by a store outage.
89
+
90
+ For the assistant, pair rate limiting with a [bot check](/docs/configuration/assistant#bot-protection) to stop scripts that spread their requests across many addresses.
@@ -5,7 +5,7 @@ description: Client-side search that works out of the box with no API keys, plus
5
5
 
6
6
  Blume ships local search with no hosted infrastructure and no API keys. It runs in the browser, works in both `blume dev` and `blume build`, and indexes only your real content — navigation chrome and excluded pages are skipped. When you outgrow it, you can switch to a hosted or semantic backend without changing how search looks or behaves — only the adapter you pass to `search` changes.
7
7
 
8
- Every backend is an **adapter** imported from `blume/search`: **Orama** (the default), **FlexSearch**, **Pagefind**, **Algolia**, **Orama Cloud**, **Typesense**, and **Mixedbread**. Each adapter owns its options, its runtime dependency, and the secrets it needs, so only the configured adapter's SDK is installed into your project — picking one backend never pulls in the others.
8
+ Every backend is an **adapter** imported from `blume/search`: **Orama** (the default), **FlexSearch**, **Pagefind**, **Algolia**, **Orama Cloud**, **Typesense**, and **Mixedbread**. Each adapter owns its options, its runtime dependency, and the secrets it needs. Orama and Pagefind ship with Blume; every other adapter's SDK is an optional peer dependency that you install yourself, so picking one backend never pulls in the others. If it's missing, `blume build` stops before Vite runs, naming the package and the command that installs it, and [`blume doctor`](/docs/cli/doctor) reports it too.
9
9
 
10
10
  ```ts blume.config.ts lineNumbers
11
11
  import { defineConfig } from "blume";
@@ -20,7 +20,7 @@ export default defineConfig({
20
20
  });
21
21
  ```
22
22
 
23
- An adapter is a plain description of the backend — not a live client — so Blume can inline it into the generated site and into an ejected project. Pass the adapter directly, or use the object form when you also want to set the [popular links](#popular-pages) or [indexing](#whats-indexed) options beside it:
23
+ An adapter is a plain description of the backend — not a live client — so Blume can inline it into the generated site and into an ejected project. Pass the adapter directly, or use the object form when you also want to set the [popular links](#popular-pages), [indexing](#whats-indexed), or [analytics](#search-analytics) options beside it:
24
24
 
25
25
  ```ts blume.config.ts lineNumbers
26
26
  import { pagefind } from "blume/search";
@@ -34,7 +34,7 @@ search: {
34
34
 
35
35
  ## Using search
36
36
 
37
- Open search with <Badge variant="accent">⌘K</Badge> (or `Ctrl K`), or press `/` when you're not typing in a field. `Esc` closes it, and `⌘J` (or `Ctrl J`) toggles the result preview pane.
37
+ Open search with the search icon in the header, with <Badge variant="accent">⌘K</Badge> (or `Ctrl K`), or by pressing `/` when you're not typing in a field. `Esc` closes it, and `⌘J` (or `Ctrl J`) toggles the result preview pane.
38
38
 
39
39
  Queries match page **titles**, **descriptions**, and **body text**, with title matches ranked highest and descriptions above body.
40
40
 
@@ -77,23 +77,66 @@ search: {
77
77
  },
78
78
  ```
79
79
 
80
- Each fence's body and title (`blume.config.ts` above) become searchable; the language and fence markers don't. On `.mdx` pages the index reads components as the text they show — a Card's title, a Tab's label, a TypeTable's descriptions — using the same serializers as the [agent surfaces](/docs/discoverability/markdown), so an `agents.markdownComponents` entry covers your own components too. The option has no effect on Pagefind or Mixedbread. Expect the index to grow with your fenced content — the client index ships to every reader, hosted adapters cap record size (Algolia rejects the sync batch when one page's record exceeds its plan's limit, leaving the previous index live), and a hit inside a fence shows flattened code in the result excerpt.
80
+ Each fence's body and title (`blume.config.ts` above) become searchable; the language and fence markers don't. On `.mdx` pages the index reads components as the text they show — a Card's title, a Tab's label, a TypeTable's descriptions — using the same serializers as the [agent surfaces](/docs/discoverability/markdown), so an `agents.markdownComponents` entry covers your own components too. The option has no effect on Pagefind or Mixedbread. Expect the index to grow with your fenced content — the client index ships to every reader, hosted adapters store more (the [Algolia](#algolia) sync splits a long page across several records), and a hit inside a fence shows flattened code in the result excerpt.
81
81
 
82
82
  On a [versioned](/docs/content/versioning) site, results default to the version being viewed, with an "All versions" toggle in the dialog footer (remembered per reader). Cross-version hits name their version on the row. Orama, FlexSearch, Algolia, and Typesense honor the scoping — hosted records carry a `version` facet, with the current docs uploaded as `"current"`. Pagefind, Orama Cloud, and Mixedbread don't scope by version: their results span every version, and the dialog leaves out the toggle.
83
83
 
84
+ On a site with [`i18n`](/docs/content/i18n), results default to the language of the page being viewed, with an "All languages" toggle beside it (also remembered per reader). Orama and FlexSearch filter their index in the browser, and Algolia, Orama Cloud, and Typesense filter on the `locale` each record carries. Pagefind keeps an index per language: it searches the page's own, and with "All languages" it merges in the others, which it then searches with the page language's word stemming. Mixedbread doesn't scope by language: its results span every language, and the dialog leaves out the toggle.
85
+
84
86
  ## Tags
85
87
 
86
- Add `search.tags` to a page's frontmatter to group it under a filter in the search dialog — readers can narrow results to a tag with a click. Tags also become a facet on the hosted adapters.
88
+ Add `search.tags` to a page's frontmatter to label it for a hosted adapter. The page's first tag is uploaded as a `tag` field on its record in Algolia, Orama Cloud, and Typesense, where you can filter on it in the provider's dashboard or your own search UI. Typesense declares `tag` as a facet; on Algolia, add it to the index's `attributesForFaceting` yourself, and the sync keeps it there.
87
89
 
88
90
  ```yaml
89
91
  search:
90
92
  tags: [api, reference]
91
93
  ```
92
94
 
95
+ The search dialog doesn't read tags. Its filters group results by the sidebar section each page is in, and they show with Orama and FlexSearch, whose indexes carry each page's section.
96
+
97
+ ## Ranking
98
+
99
+ Two frontmatter fields shape how a page ranks. `search.keywords` lists extra terms the page is found by, even when its text doesn't use them. It suits a page readers search for by an old name, an acronym, or a competitor's term. `search.boost` multiplies the page's relevance: above 1 moves it up, below 1 moves it down.
100
+
101
+ ```yaml
102
+ search:
103
+ keywords: [install, setup, getting started]
104
+ boost: 3
105
+ ```
106
+
107
+ Keep boosts to a few pages readers most often need, such as a quickstart or an overview, with values from 2 to 5. Use a value below 1, like `0.5`, for a page that should rank under others that match as well, such as a legacy guide. Keep boosts under 10: past that a boost drowns out how well a page matches, and the page tops searches it barely matches.
108
+
109
+ How much a boost moves a page depends on the adapter:
110
+
111
+ - **Orama** (the default) and **FlexSearch** multiply every match by its page's boost before sorting, so a boost of 3 counts exactly three times. The [MCP server](/docs/discoverability/mcp) and the [assistant](/docs/configuration/assistant)'s search rank the same way.
112
+ - **Pagefind** weighs a boosted page's text more heavily. It damps repeated matches, so the lift is smaller than the multiplier: a boost of 5 ranks the page higher, but closer to twice as high. Pagefind counts a boost over 10 as 10.
113
+ - **Algolia** sorts by boost among results that match about equally well, so a boost settles close calls without lifting a weaker match over a strong one. The sync adds `desc(boost)` to the index's custom ranking, after any ranking you set.
114
+ - **Typesense** sorts by relevance, then by boost among results with the same relevance score, so a boost settles ties without lifting a weaker match over a strong one.
115
+ - **Orama Cloud** fetches a wider set of results and re-sorts them by relevance times boost.
116
+ - **Mixedbread** searches your own store, which Blume doesn't upload to, so boosts and keywords don't reach it.
117
+
118
+ Keywords are searched on every adapter except Mixedbread.
119
+
120
+ ## Search analytics
121
+
122
+ With an [analytics adapter](/docs/configuration/analytics) configured, search reports what readers look for. A query is recorded once it settles, after a second without typing or when the reader picks a result or closes the dialog, as a `search` event with the `query`, the number of `results`, and the `path` it was searched from. A query with no results shows up with `results: 0`, so filtering on it lists what your docs don't cover yet. Picking a result sends a `search_select` event with the `query`, the result's 1-based `position`, its `url`, and the `path` it was picked from, for click-through.
123
+
124
+ Queries are sent as typed, up to 100 characters. Your analytics sees them the way it sees any page's search terms, so treat them as reader input: a reader can paste an email address or a key into the box, and Google Analytics and Plausible forbid sending personal data. To keep query text away from your providers, turn it off:
125
+
126
+ ```ts blume.config.ts lineNumbers
127
+ search: {
128
+ analytics: { queries: false },
129
+ },
130
+ ```
131
+
132
+ The `search` and `search_select` events then carry `queryChars`, the query's length, in place of `query`. The text travels only on the [`blume:track`](/docs/configuration/analytics#custom-events) DOM event, as `query` in `detail.props`, so a listener you write can forward it wherever you decide it belongs, the way the [assistant](/docs/configuration/assistant#analytics) handles questions. With [cookie consent](/docs/configuration/consent) on, only readers who allowed analytics are counted.
133
+
93
134
  ## Adapters
94
135
 
95
136
  The client-side adapters are keyless and need no options — their clients are configured from `i18n` (or, for Pagefind, from the built HTML), so an unknown key is rejected by config validation rather than ignored. The hosted ones take **public** credentials (safe to ship to the browser) and read their **secret** admin key from an environment variable at build time — the secret never lands in the config or the client bundle. Every option you pass to a hosted adapter is kept verbatim and handed to its search client in the browser (or, for Mixedbread, to the search endpoint), so an option Blume doesn't name still reaches the SDK — Algolia's `liteClient`, the Typesense `Client`, or `OramaClient`. Those options must be JSON values — the descriptor is inlined into the generated project as a literal, so a function, `undefined`, or a bigint fails config validation with a path instead of vanishing on the way.
96
137
 
138
+ Algolia, Orama Cloud, and Typesense upload your pages to their index at the end of each `blume build`. When the admin key isn't set, the build warns and skips the upload, so a build without secrets, such as a local one, still works. When the key is set and the upload fails (a wrong key, an unreachable host, a rejected record), the build fails with `BLUME_SEARCH_SYNC_FAILED` instead of deploying against an index it didn't update.
139
+
97
140
  ### Orama (default)
98
141
 
99
142
  Blume's default engine. It builds a JSON index served at `/blume-search.json` and queries it in the browser — instant, client-side, and live in `blume dev` as you edit. No keys, no service. Omitting `search` entirely selects it; spell it out only when you want to be explicit:
@@ -106,7 +149,7 @@ search: orama(),
106
149
 
107
150
  #### Non-Latin scripts
108
151
 
109
- Orama's standard tokenizer keeps only basic Latin letters, digits and a handful of accented vowels, so text in any other script — Japanese, Chinese, Korean and Thai, but equally Russian, Greek, Hebrew and Hindi — would otherwise produce no matches at all. Blume handles this for you: when [`i18n.defaultLocale`](/docs/content/i18n) resolves to a non-Latin script, the index switches to a word-segmenting tokenizer (built on the browser- and Node-native `Intl.Segmenter`). Declaring your site's language is all it takes:
152
+ Orama's standard tokenizer keeps only basic Latin letters, digits and a handful of accented vowels, so text in any other script — Japanese, Chinese, Korean and Thai, but equally Russian, Greek, Hebrew and Hindi — would otherwise produce no matches at all. Blume handles this for you: pages in a locale that resolves to a non-Latin script are indexed with a word-segmenting tokenizer (built on the browser- and Node-native `Intl.Segmenter`). Declaring your site's languages is all it takes:
110
153
 
111
154
  ```ts blume.config.ts lineNumbers
112
155
  i18n: {
@@ -115,13 +158,13 @@ i18n: {
115
158
  }
116
159
  ```
117
160
 
118
- The same tokenizer serves the search dialog, the MCP server's `search_docs` tool, and assistant grounding. The script is what decides, not the language name — `az-Cyrl` is segmented while `sr-Latn` is not — and it is the default locale that decides for the whole index: on a mixed-language site every page shares the default locale's tokenizer. With a non-Latin default that's safe, because Latin words survive segmentation intact, so pages in English stay searchable alongside the default language. The reverse doesn't hold: non-Latin translations on a Latin-default site aren't searchable. Latin-script languages that lean heavily on diacritics (Vietnamese, or Serbian in Latin script) also fare worse on the standard tokenizer, which folds only a few accented vowels and splits words on the rest.
161
+ The same tokenizers serve the search dialog, the MCP server's `search_docs` tool, and assistant grounding. The script is what decides, not the language name — `az-Cyrl` is segmented while `sr-Latn` is not. With a non-Latin default, every page shares the default locale's tokenizer: Latin words survive segmentation intact, so pages in English stay searchable alongside the default language. With a Latin default, Latin-script pages keep the standard tokenizer and each translation in a non-Latin script gets its own locale's, so an English site's Japanese and Hindi pages are searchable from their own language and from a search across every language. Latin-script languages that lean heavily on diacritics (Vietnamese, or Serbian in Latin script) also fare worse on the standard tokenizer, which folds only a few accented vowels and splits words on the rest.
119
162
 
120
163
  Japanese and Chinese go one step further. Segmenting alone indexes a compound term as its parts — 資金決済法 as 資金, 決済 and 法 — which lets a page mentioning each part somewhere outrank the page the term is actually about. Han, Hiragana and Katakana are therefore indexed as overlapping character pairs, and queries on those indexes prefer pages carrying a term's pairs together, loosening to any-pair matching when no page carries them all, so typing a whole sentence still returns its closest pages. Korean and Thai keep their segmented words.
121
164
 
122
165
  ### FlexSearch
123
166
 
124
- A second keyless, client-side option. It reuses the same `/blume-search.json` index Orama ships and builds a [FlexSearch](https://github.com/nextapps-de/flexsearch) document index in the browser. Works in `blume dev` and `blume build`.
167
+ A second keyless, client-side option (install `flexsearch`). It reuses the same `/blume-search.json` index Orama ships and builds a [FlexSearch](https://github.com/nextapps-de/flexsearch) document index in the browser. Works in `blume dev` and `blume build`.
125
168
 
126
169
  FlexSearch has no equivalent segmentation hook, so for sites in a non-Latin script prefer Orama (the default) or [Pagefind](#pagefind), whose `pagefind_extended` binary indexes a broad set of languages and segments Chinese, Japanese and Korean natively.
127
170
 
@@ -145,7 +188,9 @@ Pagefind only runs during `blume build`, so search isn't available in `blume dev
145
188
 
146
189
  ### Algolia
147
190
 
148
- The browser queries [Algolia](https://www.algolia.com) directly with your search-only key. Each `blume build` replaces the index using the admin key from `ALGOLIA_ADMIN_API_KEY` (the build warns and skips the upload if it's unset). The whole index is replaced on every sync, so pages you delete or rename don't linger as stale results. The sync also adds `filterOnly(locale)` and `filterOnly(version)` to the index's `attributesForFaceting`, keeping any facets you declared yourself, because the dialog scopes results by language and docs version, and in Algolia a filter on an attribute that isn't declared for faceting matches nothing.
191
+ Install `algoliasearch`. The browser queries [Algolia](https://www.algolia.com) directly with your search-only key. Each `blume build` replaces the index using the admin key from `ALGOLIA_ADMIN_API_KEY` (the build warns and skips the upload if it's unset). The whole index is replaced on every sync, so pages you delete or rename don't linger as stale results. The sync also adds `filterOnly(locale)` and `filterOnly(version)` to the index's `attributesForFaceting`, keeping any facets you declared yourself, because the dialog scopes results by language and docs version, and in Algolia a filter on an attribute that isn't declared for faceting matches nothing.
192
+
193
+ Algolia caps a record at 10 KB on its Build and Grow plans, so the sync splits a longer page into several records, each carrying the page's title, description, and URL with a stretch of its text. It then sets the index's `attributeForDistinct` to `url`, unless you set your own, so a split page appears once in results, at its best-matching record.
149
194
 
150
195
  ```ts blume.config.ts lineNumbers
151
196
  import { algolia } from "blume/search";
@@ -159,7 +204,7 @@ search: algolia({
159
204
 
160
205
  ### Orama Cloud
161
206
 
162
- Hosted Orama. The browser queries your index endpoint with the public API key; `blume build` pushes records to the index using `ORAMA_PRIVATE_API_KEY`. Set `indexId` to enable the sync.
207
+ Hosted Orama (install `@oramacloud/client`). The browser queries your index endpoint with the public API key; `blume build` pushes records to the index using `ORAMA_PRIVATE_API_KEY`. Set `indexId` to enable the sync; without it, the build warns and skips the sync even with the key set.
163
208
 
164
209
  ```ts blume.config.ts lineNumbers
165
210
  import { oramaCloud } from "blume/search";
@@ -173,7 +218,7 @@ search: oramaCloud({
173
218
 
174
219
  ### Typesense
175
220
 
176
- Self-hosted or cloud [Typesense](https://typesense.org). The browser queries the collection with the search-only key; `blume build` recreates the collection and imports documents using `TYPESENSE_ADMIN_API_KEY`. The collection is dropped and rebuilt on every sync so deleted or renamed pages don't linger as stale results — if you hand-tune the collection's settings, reapply them after a build.
221
+ Self-hosted or cloud [Typesense](https://typesense.org). Install `typesense`. The browser queries the collection with the search-only key. Each `blume build` imports your pages into a new collection using `TYPESENSE_ADMIN_API_KEY`, then points an [alias](https://typesense.org/docs/latest/api/collection-alias.html) named after `collection` at it and drops the collection it replaced. Searches keep reading the previous collection until the new one is complete, and deleted or renamed pages don't linger as stale results. If the sync fails, the build fails and the alias keeps serving the last complete sync. The admin key needs to manage collections and aliases. On the first sync, a collection that already has the `collection` name (from an earlier Blume version) is replaced by the alias, and search keys scoped to that name keep working. Settings you hand-tune on a collection don't carry over to the next one, so reapply them after a build.
177
222
 
178
223
  ```ts blume.config.ts lineNumbers
179
224
  import { typesense } from "blume/search";
@@ -186,9 +231,13 @@ search: typesense({
186
231
  }),
187
232
  ```
188
233
 
234
+ Typesense's default tokenizer splits text on spaces, so in Chinese, Japanese, or Thai a query only matches from the start of a run of text. Set `locale` to your pages' language as a two-letter code (`locale: "ja"`, `"zh"`, `"th"`) and the sync tokenizes the searched fields for it; Latin-script words in those fields still match. One collection holds every language, so the setting applies to all of them.
235
+
189
236
  ### Mixedbread
190
237
 
191
- Semantic search via [Mixedbread](https://www.mixedbread.com). Queries are proxied through a generated `/api/search` endpoint that holds your key, so this adapter **requires server output**: a host adapter such as `deployment: vercel()` from `blume/deploy` (see [Server rendering](/docs/deployment#server-rendering)). The endpoint reads `MIXEDBREAD_API_KEY`. Sync your content to the store with the Mixedbread CLI in your build, e.g. `mxbai vs sync <STORE_ID> ./content --ci`.
238
+ Semantic search via [Mixedbread](https://www.mixedbread.com) (install `@mixedbread/sdk`). Queries are proxied through a generated `/api/search` endpoint that holds your key, so this adapter **requires server output**: a host adapter such as `deployment: vercel()` from `blume/deploy` (see [Server rendering](/docs/deployment#server-rendering)). The endpoint reads `MIXEDBREAD_API_KEY`. Blume doesn't upload to the store: sync your content folder with the [Mixedbread CLI](https://www.npmjs.com/package/@mixedbread/cli) as part of your build, e.g. `mxbai store sync <STORE_ID> ./docs --yes` (the CLI reads its key from `MXBAI_API_KEY`).
239
+
240
+ Each result links to the page its file renders, found by the path the CLI records for the file, so run the sync from your project root or a folder above it, such as a monorepo root. A page appears once, at its best-matching passage, and passages from files that aren't pages, or from pages excluded from search, are left out.
192
241
 
193
242
  ```ts blume.config.ts lineNumbers
194
243
  import { mixedbread } from "blume/search";
@@ -198,6 +247,8 @@ search: mixedbread({
198
247
  }),
199
248
  ```
200
249
 
250
+ The search dialog waits for the reader to pause typing before it sends a query, and drops a query the reader has typed past. Each query sent counts toward the endpoint's [rate limit](/docs/configuration/rate-limiting).
251
+
201
252
  ### Disabling search
202
253
 
203
254
  ```ts blume.config.ts lineNumbers
@@ -32,10 +32,12 @@ theme: {
32
32
  }
33
33
  ```
34
34
 
35
- Named presets: `blue` (default), `green`, `orange`, `pink`, `purple`, `red`, and `teal`.
35
+ Named presets: `blue` (default), `green`, `orange`, `pink`, `purple`, `red`, and `teal`. Each preset has a darker light-mode shade and a lighter dark-mode shade, so accent text and button labels meet WCAG AA contrast (4.5:1) in both modes.
36
36
 
37
37
  A string applies to both color modes; pass an object for [a different accent per mode](#dark-mode-colors).
38
38
 
39
+ Text on an accent or action fill, like a button label or a step number, is white while white meets AA on that color, and dark otherwise. A light accent gets dark labels without any extra config. [`blume audit`](/docs/cli/audit#theme-contrast-low) checks a custom accent, action, or background against the same bar.
40
+
39
41
  ### Radius
40
42
 
41
43
  `radius` sets the corner rounding shared by cards, code blocks, callouts, and inputs — `none`, `sm`, `md` (default), or `lg`.