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
@@ -4,13 +4,17 @@ import { pathToFileURL } from "node:url";
4
4
  import { dirname, isAbsolute, join, relative } from "pathe";
5
5
 
6
6
  import type { AskRetrievalOptions } from "../ai/ask-context.ts";
7
+ import { ASK_MAX_MESSAGES, ASK_MAX_MESSAGES_CHARS } from "../ai/ask-limits.ts";
7
8
  import type { AskBackend } from "../ai/ask.ts";
8
9
  import { buildHomeLinkHeader } from "../ai/link-headers.ts";
9
10
  import type { CaptchaAdapter } from "../captcha/schema.ts";
10
11
  import { CONSENT_CLIENT_MODULES } from "../consent/clients.ts";
11
12
  import { normalizeBasePath } from "../core/base-path.ts";
12
13
  import { TOC_HIDDEN_KEY } from "../core/heading-markers.ts";
13
- import { compileRedirects, isPatternPath } from "../core/redirect-patterns.ts";
14
+ import {
15
+ compileEveryRedirect,
16
+ isPatternPath,
17
+ } from "../core/redirect-patterns.ts";
14
18
  import type { CompiledRedirect } from "../core/redirect-patterns.ts";
15
19
  import type { ResolvedConfig } from "../core/schema.ts";
16
20
  import { resolveDocsCollection } from "../core/sources/collection.ts";
@@ -22,7 +26,10 @@ import { deployPassthrough } from "../deploy/adapters/types.ts";
22
26
  import { SVG_ASSET_HEADERS } from "../deploy/headers.ts";
23
27
  import { deployPlatform } from "../deploy/platforms/index.ts";
24
28
  import { adapterRoot, distDir } from "../deploy/platforms/paths.ts";
25
- import { applyBaseToAstroRedirects } from "../deploy/redirects.ts";
29
+ import {
30
+ applyBaseToAstroRedirects,
31
+ withMirrorRedirects,
32
+ } from "../deploy/redirects.ts";
26
33
  import { API_RAIL_KEY } from "../markdown/api-rail.ts";
27
34
  import { VIEWS_KEY } from "../markdown/views.ts";
28
35
  import type { OgCache } from "../og/cache.ts";
@@ -34,6 +41,7 @@ import type {
34
41
  ResolvedSearchAdapter,
35
42
  SearchAdapterKind,
36
43
  } from "../search/adapters/registry.ts";
44
+ import type { SourcePage } from "../search/source-pages.ts";
37
45
  import { buildFontEntries, fontLocaleCodes } from "../theme/fonts.ts";
38
46
  import { importSpecifier, wrapperPropsType } from "./component-slots.ts";
39
47
  import type { ExampleSpec } from "./examples.ts";
@@ -122,7 +130,7 @@ const renderAstroAdapter = (
122
130
  const { astro } = platform;
123
131
  const args = {
124
132
  ...astro.options(context),
125
- ...deployPassthrough(deployment.options),
133
+ ...deployPassthrough(deployment.options, astro.configOptions),
126
134
  };
127
135
  const argsLiteral = Object.keys(args).length > 0 ? JSON.stringify(args) : "";
128
136
  const construct = `adapter(${argsLiteral})`;
@@ -133,7 +141,7 @@ const renderAstroAdapter = (
133
141
  ? `withAdapterRoot(${construct}, ${JSON.stringify(adapterRoot(context))})`
134
142
  : construct;
135
143
  return {
136
- configEntries: Object.entries(astro.config)
144
+ configEntries: Object.entries(astro.config(deployment.options))
137
145
  .map(([key, value]) => `\n ${key}: ${JSON.stringify(value)},`)
138
146
  .join(""),
139
147
  importLine: `import adapter from "${astro.package}";\n`,
@@ -283,18 +291,14 @@ const renderUserAliases = (
283
291
  .join("");
284
292
 
285
293
  /**
286
- * Excludes Vite's pre-bundled dep cache from @vitejs/plugin-react. Astro's
287
- * react() replaces the plugin's default `/node_modules/` exclude with just
288
- * `/\.astro$/`, so without this Babel re-parses every optimized dep chunk
289
- * served from `.vite/deps` — a 500KB+ vendor bundle per chunk, re-done on each
290
- * re-optimization. A blanket `/node_modules/` exclude would instead switch the
291
- * React Compiler off for Blume's own components in published installs (they
292
- * resolve under `node_modules/blume/src`, and exclude beats include in the
293
- * plugin's filter), so only the pre-bundle cache is excluded. The hidden runtime
294
- * relocates that cache to `<runtime>/.cache/vite` (see `cacheOptions`), so both
295
- * the default and the relocated path are excluded.
294
+ * Excludes the hidden runtime's pre-bundled dep cache from @vitejs/plugin-react.
295
+ * Astro's react() already excludes `/node_modules/`, which covers Vite's
296
+ * default `node_modules/.vite` cache, but the hidden runtime relocates that
297
+ * cache to `<runtime>/.cache/vite` (see `cacheOptions`). Without this the React
298
+ * Compiler re-transforms every optimized dep chunk served from there — a 500KB+
299
+ * vendor bundle per chunk, re-done on each re-optimization.
296
300
  */
297
- const REACT_EXCLUDE = String.raw`exclude: [/\/node_modules\/\.vite\//, /\/\.cache\/vite\//]`;
301
+ const REACT_EXCLUDE = String.raw`exclude: [/\/\.cache\/vite\//]`;
298
302
 
299
303
  /**
300
304
  * The `cacheDir` entries for the generated config's top level and its `vite`
@@ -324,17 +328,14 @@ const runtimeCacheOptions = (
324
328
  };
325
329
 
326
330
  /**
327
- * The `react()` integration call. When `compilerPath` is set (the resolved
328
- * absolute path to `babel-plugin-react-compiler`), react() carries the compiler
329
- * as the first babel plugin — an absolute path, because @vitejs/plugin-react
330
- * resolves babel plugins from the *project* root, not `.blume/`, so a bare
331
- * specifier wouldn't resolve in a user project. `target: "19"` matches Blume's
332
- * React pin. `null`/`undefined` (compiler off or unresolvable) omits the babel
333
- * block. Both variants carry the pre-bundle exclude above.
331
+ * The `react()` integration call. `compiler` turns on @astrojs/react's React
332
+ * Compiler, which runs on `oxc-transform-react` and targets the installed
333
+ * React major; false/absent (compiler off or unresolvable) leaves it out. Both
334
+ * variants carry the pre-bundle exclude above.
334
335
  */
335
- const reactIntegration = (compilerPath: string | null | undefined): string =>
336
- compilerPath
337
- ? `react({ babel: { plugins: [[${JSON.stringify(compilerPath)}, { target: "19" }]] }, ${REACT_EXCLUDE} })`
336
+ const reactIntegration = (compiler: boolean | undefined): string =>
337
+ compiler
338
+ ? `react({ compiler: true, ${REACT_EXCLUDE} })`
338
339
  : `react({ ${REACT_EXCLUDE} })`;
339
340
 
340
341
  interface IntegrationBridgeOptions {
@@ -388,11 +389,10 @@ interface OptimizeDepsConfig {
388
389
  * the Vite root is the generated runtime, so user pages, convention islands,
389
390
  * and alias-reachable components all live outside it and are otherwise only
390
391
  * crawled when first requested. The compiler runtime rides the include list
391
- * because it is Babel-injected and no source scan can see it. @vitejs/plugin-react
392
- * would add it itself, but only when the babel plugin is passed by its bare
393
- * name (`getReactCompilerPlugin` is an exact string match) — Blume passes an
394
- * absolute path (see `reactIntegration`), which that check never matches. See
395
- * the optimizeDeps comment in the generated config for the failure this prevents.
392
+ * because the compiler injects its import and no source scan can see it. The
393
+ * compiler plugin in `@vitejs/plugin-react` lists it too; Blume keeps it
394
+ * explicit so the guard doesn't hinge on that plugin's internals. See the
395
+ * optimizeDeps comment in the generated config for the failure this prevents.
396
396
  */
397
397
  /**
398
398
  * The client-side libraries a site needs, decided at generation time. A
@@ -432,7 +432,7 @@ const resolveOptimizeDeps = (options: {
432
432
  context: ProjectContext;
433
433
  features: ClientFeatures;
434
434
  needsReact: boolean;
435
- reactCompilerPath: string | null | undefined;
435
+ reactCompiler: boolean | undefined;
436
436
  searchKind: SearchAdapterKind;
437
437
  }): OptimizeDepsConfig => {
438
438
  const { context, features } = options;
@@ -456,7 +456,7 @@ const resolveOptimizeDeps = (options: {
456
456
  // (__PREFETCH_PREFETCH_ALL__ and friends) that a pre-bundled copy loses,
457
457
  // throwing ReferenceError on every page. Astro manages their optimization
458
458
  // itself, without a mid-session reload.
459
- ...(options.needsReact && options.reactCompilerPath
459
+ ...(options.needsReact && options.reactCompiler
460
460
  ? ["react/compiler-runtime"]
461
461
  : []),
462
462
  ];
@@ -567,7 +567,10 @@ const blumeIntegrationOptions = (options: {
567
567
  pages: BlumePageRoute[];
568
568
  redirects: CompiledRedirect[];
569
569
  }): BlumeIntegrationOptions => {
570
- const shared: BlumeIntegrationOptions = { pages: options.pages };
570
+ const shared: BlumeIntegrationOptions = {
571
+ pages: options.pages,
572
+ poweredBy: options.config.poweredBy,
573
+ };
571
574
  if (options.redirects.length > 0) {
572
575
  shared.redirects = options.redirects;
573
576
  }
@@ -612,11 +615,10 @@ export const astroConfigTemplate = (options: {
612
615
  */
613
616
  generatedModulesDir?: string;
614
617
  /**
615
- * Absolute path to `babel-plugin-react-compiler` when the React Compiler is
616
- * enabled (resolved from Blume's package root by the caller); null/absent
617
- * disables the compiler and emits a bare `react()`.
618
+ * Turn on the React Compiler (the caller checked that `oxc-transform-react`
619
+ * resolves); false/absent emits `react()` without it.
618
620
  */
619
- reactCompilerPath?: string | null;
621
+ reactCompiler?: boolean;
620
622
  /** Project tsconfig path aliases (`find` -> absolute dir), e.g. `@` -> src. */
621
623
  aliases?: Record<string, string>;
622
624
  /**
@@ -664,7 +666,7 @@ export const astroConfigTemplate = (options: {
664
666
  context,
665
667
  features,
666
668
  needsReact,
667
- reactCompilerPath: options.reactCompilerPath,
669
+ reactCompiler: options.reactCompiler,
668
670
  searchKind: config.search.provider.kind,
669
671
  });
670
672
 
@@ -700,11 +702,17 @@ export const astroConfigTemplate = (options: {
700
702
  // Base the redirect paths the same way routes are based, so a redirect lands
701
703
  // under `basePath` too. Astro layers its own `base` (deployment.base) onto
702
704
  // `from` when matching, but never onto `to` — see applyBaseToAstroRedirects.
703
- const basedRedirects = applyBaseToAstroRedirects(
704
- config.redirects,
705
- config.basePath,
706
- deployment.options.base ?? "",
707
- new Set(contentRoutes)
705
+ // A moved page's Markdown copies move with it (see withMirrorRedirects).
706
+ const redirectPages = new Set(contentRoutes);
707
+ const basedRedirects = withMirrorRedirects(
708
+ applyBaseToAstroRedirects(
709
+ config.redirects,
710
+ config.basePath,
711
+ deployment.options.base ?? "",
712
+ redirectPages
713
+ ),
714
+ redirectPages,
715
+ { from: "", to: normalizeBasePath(deployment.options.base) }
708
716
  );
709
717
  // Only exact redirects: Astro can't prerender a pattern's redirect pages
710
718
  // (it would need every path the pattern covers), so a pattern reaches the
@@ -837,7 +845,7 @@ export const astroConfigTemplate = (options: {
837
845
  `mdx({ processor: blumeMdxProcessor(${processorOptions}) })`,
838
846
  ];
839
847
  if (needsReact) {
840
- integrations.push(reactIntegration(options.reactCompilerPath));
848
+ integrations.push(reactIntegration(options.reactCompiler));
841
849
  }
842
850
  if (needsVue) {
843
851
  integrations.push("vue()");
@@ -855,7 +863,7 @@ export const astroConfigTemplate = (options: {
855
863
  contentRoutes,
856
864
  ejected,
857
865
  pages,
858
- redirects: compileRedirects(basedRedirects),
866
+ redirects: compileEveryRedirect(basedRedirects),
859
867
  })
860
868
  )})`
861
869
  );
@@ -931,8 +939,8 @@ ${userConfigSetup}export default defineConfig({
931
939
  // Everything hydration can reach must be part of the dev dep optimizer's
932
940
  // FIRST run. The Vite root is the generated runtime, so user pages,
933
941
  // islands, and aliased components live outside it and are only crawled
934
- // when first requested — and \`react/compiler-runtime\` is Babel-injected,
935
- // so no source scan can ever see it. A dependency discovered after
942
+ // when first requested — and \`react/compiler-runtime\` is injected by the
943
+ // React Compiler, so no source scan can ever see it. A dependency discovered after
936
944
  // hydration begins triggers a mid-session re-optimization whose new
937
945
  // generation imports React through new \`?v=\` URLs; the browser then
938
946
  // evaluates a second React copy and every island tears down with
@@ -1270,8 +1278,8 @@ const askCaptchaTemplate = (adapter?: CaptchaAdapter): RateLimitTemplate => {
1270
1278
 
1271
1279
  /**
1272
1280
  * Largest request body the assistant route reads: 64 KB, well above the
1273
- * 24,000-character message budget it validates next, so a real conversation
1274
- * never meets it.
1281
+ * message budget it validates next (`ai/ask-limits.ts`), so a real
1282
+ * conversation never meets it.
1275
1283
  */
1276
1284
  const ASK_BODY_LIMIT_BYTES = 65_536;
1277
1285
 
@@ -1380,7 +1388,7 @@ ${limit.check} const text = await readCappedText(request, ${ASK_BODY_LIMIT_BYTE
1380
1388
  const valid =
1381
1389
  Array.isArray(raw) &&
1382
1390
  raw.length > 0 &&
1383
- raw.length <= 40 &&
1391
+ raw.length <= ${ASK_MAX_MESSAGES} &&
1384
1392
  raw.every(
1385
1393
  (m: unknown) =>
1386
1394
  typeof m === "object" &&
@@ -1388,10 +1396,10 @@ ${limit.check} const text = await readCappedText(request, ${ASK_BODY_LIMIT_BYTE
1388
1396
  ("role" in m && (m.role === "user" || m.role === "assistant")) &&
1389
1397
  ("content" in m && typeof m.content === "string")
1390
1398
  ) &&
1391
- JSON.stringify(raw).length <= 24_000;
1399
+ JSON.stringify(raw).length <= ${ASK_MAX_MESSAGES_CHARS};
1392
1400
  if (!valid) {
1393
1401
  return new Response(
1394
- "Invalid request: send 1-40 user/assistant messages with string content.",
1402
+ "Invalid request: send 1-${ASK_MAX_MESSAGES} user/assistant messages with string content.",
1395
1403
  { status: 400 }
1396
1404
  );
1397
1405
  }
@@ -1649,10 +1657,12 @@ export const searchClientTemplate = (config: ResolvedConfig): string => {
1649
1657
  return hostedSearchClient(provider);
1650
1658
  }
1651
1659
  case "server": {
1652
- return `${SEARCH_CLIENT_HEADER}${searchClientImport("endpoint")}${SEARCH_BASE_IMPORT}
1660
+ // The dialog passes `typing`, which paces its queries (see endpoint.ts).
1661
+ return `${SEARCH_CLIENT_HEADER}${searchClientImport("endpoint")}${SEARCH_BASE_IMPORT}import type { SearchClientOptions } from "blume/components/layout/search/types.ts";
1653
1662
  const api = joinBase(import.meta.env.BASE_URL, "api/search");
1654
1663
 
1655
- export const createSearch = () => create({ api });
1664
+ export const createSearch = (options: SearchClientOptions = {}) =>
1665
+ create({ ...options, api });
1656
1666
  `;
1657
1667
  }
1658
1668
  case "pagefind": {
@@ -1674,14 +1684,17 @@ export const createSearch = () => create({ url });
1674
1684
  /**
1675
1685
  * Generate the Mixedbread search endpoint (`/api/search`). It holds the secret
1676
1686
  * key server-side and proxies semantic queries to the configured store. The
1677
- * adapter's options are inlined as a literal so the route never imports the
1678
- * config. The result mapping is best-effort and may need tuning to how your
1679
- * content was synced (see the Mixedbread sync step / \`mxbai vs sync\`).
1687
+ * adapter's options are inlined as literals so the route never imports the
1688
+ * config. `pages` (see `sourcePages`) maps each source file to its page: a
1689
+ * chunk links to the page whose file `mxbai store sync` uploaded it from,
1690
+ * found by the longest trailing run of the path the CLI recorded.
1680
1691
  */
1681
1692
  export const mixedbreadSearchEndpointTemplate = (
1682
1693
  options: MixedbreadOptions,
1694
+ pages: [string, SourcePage][],
1683
1695
  rateLimit?: RateLimitAdapter | null
1684
1696
  ): string => {
1697
+ const { search_options: tuning, storeId, ...searchOptions } = options;
1685
1698
  const limit = rateLimitTemplate(rateLimit, "search");
1686
1699
  const imports = [
1687
1700
  'import type { APIRoute } from "astro";',
@@ -1696,9 +1709,26 @@ ${imports.join("\n")}
1696
1709
  export const prerender = false;
1697
1710
 
1698
1711
  const client = new Mixedbread({ apiKey: getSecret("MIXEDBREAD_API_KEY") ?? "" });
1699
- const OPTIONS = ${JSON.stringify(options)};
1700
- // Every option besides the store reaches the search call verbatim.
1701
- const { storeId: STORE_ID, ...SEARCH_OPTIONS } = OPTIONS;
1712
+ const STORE_ID = ${JSON.stringify(storeId)};
1713
+ // Every other option reaches the search call verbatim.
1714
+ const SEARCH_OPTIONS = ${JSON.stringify(searchOptions)};
1715
+ const SEARCH_TUNING = ${JSON.stringify(tuning ?? {})};
1716
+ // Each page's source file, relative to the project root, and the page it renders.
1717
+ const PAGES = new Map<string, { title: string; url: string }>(${JSON.stringify(pages)});
1718
+
1719
+ // The page a synced file renders. \`mxbai store sync\` records the path
1720
+ // relative to where it ran (the project root, or a monorepo root above it), so
1721
+ // the longest trailing run of that path that names a page's file wins.
1722
+ const pageFor = (path: string) => {
1723
+ const segments = path.replaceAll("\\\\", "/").split("/");
1724
+ for (let start = 0; start < segments.length; start += 1) {
1725
+ const page = PAGES.get(segments.slice(start).join("/"));
1726
+ if (page) {
1727
+ return page;
1728
+ }
1729
+ }
1730
+ return undefined;
1731
+ };
1702
1732
  ${limit.setup}
1703
1733
  export const POST: APIRoute = async (context) => {
1704
1734
  const { request } = context;
@@ -1724,23 +1754,32 @@ ${limit.check} // A search body is one short query: read it under a 16 KB cap,
1724
1754
  });
1725
1755
  }
1726
1756
  // \`top_k\` defaults to 8 unless an option sets it; the query and store
1727
- // always come from the request and \`storeId\`.
1757
+ // always come from the request and \`storeId\`. File metadata is always
1758
+ // returned: it holds the path \`mxbai store sync\` recorded for the file.
1728
1759
  const response = await client.stores.search({
1729
1760
  top_k: 8,
1730
1761
  ...SEARCH_OPTIONS,
1731
1762
  query,
1763
+ search_options: { ...SEARCH_TUNING, return_metadata: true },
1732
1764
  store_identifiers: [STORE_ID],
1733
1765
  });
1734
- const hits = (response.data ?? []).map((chunk) => {
1766
+ const seen = new Set<string>();
1767
+ const hits = (response.data ?? []).flatMap((chunk) => {
1768
+ const { file_path: path } = (chunk.metadata ?? {}) as { file_path?: unknown };
1769
+ const page = typeof path === "string" ? pageFor(path) : undefined;
1770
+ // A file no page renders has nowhere to link, and a page's later chunks
1771
+ // would repeat it: one hit per page, at its best chunk.
1772
+ if (!page || seen.has(page.url)) {
1773
+ return [];
1774
+ }
1775
+ seen.add(page.url);
1735
1776
  const meta = chunk.generated_metadata ?? {};
1736
1777
  // Only a text chunk carries \`text\`; image, audio, and video chunks fall
1737
1778
  // back to the excerpt the store generated for them.
1738
1779
  const text = "text" in chunk ? chunk.text : undefined;
1739
- return {
1740
- excerpt: text ?? meta.excerpt ?? "",
1741
- title: meta.title ?? chunk.filename ?? "",
1742
- url: meta.url ?? "",
1743
- };
1780
+ return [
1781
+ { excerpt: text ?? meta.excerpt ?? "", title: page.title, url: page.url },
1782
+ ];
1744
1783
  });
1745
1784
  return new Response(JSON.stringify(hits), {
1746
1785
  headers: { "Content-Type": "application/json" },
@@ -2417,6 +2456,7 @@ import type { CollectionKey } from "astro:content";
2417
2456
  import RootLayout from "blume/components/layout/RootLayout.astro";
2418
2457
  import { withBase, withMountedBase } from "blume/components/islands/base-path.ts";
2419
2458
  import { mountBasePath, stripBasePath } from "blume/core/base-path.ts";
2459
+ import { routeSetFor, servesRoute } from "blume/core/locale-links.ts";
2420
2460
  import { resolveSlot } from "blume/components/layout/overrides.ts";
2421
2461
  ${componentImports}
2422
2462
  import data from "blume:data";
@@ -2620,18 +2660,24 @@ const logicalRoute = i18n
2620
2660
  ? stripLocale(stripBasePath(data.config.basePath, route), locale)
2621
2661
  : route;
2622
2662
  // A page from a one-language source (GitHub Releases) gets no switcher: every
2623
- // other locale would only repeat the same text.
2663
+ // other locale would only repeat the same text. A locale with no real
2664
+ // translation links the page's fallback copy, which exists only while
2665
+ // fallbacks are on; where nothing is served at that URL (\`fallbackLocale:
2666
+ // null\`), the locale is left out rather than linked to a 404.
2624
2667
  const localeSwitch = i18n && !monolingual
2625
- ? i18n.locales.map((l) => {
2668
+ ? i18n.locales.flatMap((l) => {
2626
2669
  const alt = (alternates ?? []).find((x) => x.locale === l.code);
2627
- return {
2628
- code: l.code,
2629
- current: l.code === locale,
2630
- dir: l.dir,
2631
- href: alt ? alt.path : mountLocalized(logicalRoute, l.code),
2632
- label: l.label,
2633
- untranslated: !alt,
2634
- };
2670
+ const href = alt ? alt.path : mountLocalized(logicalRoute, l.code);
2671
+ return alt || servesRoute(routeSetFor(data.routes), href)
2672
+ ? [{
2673
+ code: l.code,
2674
+ current: l.code === locale,
2675
+ dir: l.dir,
2676
+ href,
2677
+ label: l.label,
2678
+ untranslated: !alt,
2679
+ }]
2680
+ : [];
2635
2681
  })
2636
2682
  : [];
2637
2683
 
@@ -2958,13 +3004,15 @@ const canonical = base ? base + basedRoute : null;
2958
3004
  const ogPath = data.config.og.enabled ? withMountedBase("/og/changelog.png") : null;
2959
3005
  const ogImage = ogPath && base ? base + ogPath : ogPath;
2960
3006
 
2961
- // The page chrome (h1, title, description) comes from the translatable
2962
- // \`changelog\` group; optional chaining tolerates a not-yet-regenerated data
2963
- // snapshot from before these keys existed.
3007
+ // The page chrome (h1, title, description, empty state) comes from the
3008
+ // translatable \`changelog\` group, which carries the \`changelog\` config's title and
3009
+ // description when it sets them; optional chaining tolerates a
3010
+ // not-yet-regenerated data snapshot from before these keys existed.
2964
3011
  const changelogTitle = data.ui.changelog?.title ?? "Changelog";
2965
3012
  const changelogDescription =
2966
3013
  data.ui.changelog?.description ??
2967
3014
  "Product updates, new features, and fixes from every release.";
3015
+ const changelogEmpty = data.ui.changelog?.empty ?? "No changelog entries yet.";
2968
3016
  // The layout suffixes "- {site title}" itself, so the page title is just the
2969
3017
  // changelog's own name — prefixing the site title too would double it
2970
3018
  // ("Acme Changelog - Acme").
@@ -3017,7 +3065,7 @@ const LayoutComponent = resolveSlot(layoutOverrides.Layout, RootLayout);
3017
3065
  <p class="text-lg text-muted-foreground">{changelogDescription}</p>
3018
3066
  {
3019
3067
  items.length === 0 ? (
3020
- <p>No changelog entries yet.</p>
3068
+ <p>{changelogEmpty}</p>
3021
3069
  ) : (
3022
3070
  <div class="not-prose mt-10 divide-y divide-border border-border border-y">
3023
3071
  {groups.map((group) => (
@@ -161,7 +161,7 @@ export const CHECKS = [
161
161
  },
162
162
  {
163
163
  category: "duplicates",
164
- fix: "Merge the pages, or set `seo.canonical` on all but one.",
164
+ fix: "Merge the pages, or point `seo.canonical` on all but one at the page to index.",
165
165
  id: "BLUME_AUDIT_DUPLICATE_CONTENT",
166
166
  severity: "warning",
167
167
  tier: "static",
@@ -359,7 +359,7 @@ export const CHECKS = [
359
359
  },
360
360
  {
361
361
  category: "redirects",
362
- fix: "Remove the redirect, or delete the page it shadows — the page wins and the redirect never fires.",
362
+ fix: "Remove the redirect, or delete the page it shadows — a path can't be both, and which one readers get depends on the host.",
363
363
  id: "BLUME_AUDIT_REDIRECT_SOURCE_IS_PAGE",
364
364
  severity: "error",
365
365
  tier: "static",
@@ -373,6 +373,14 @@ export const CHECKS = [
373
373
  tier: "network",
374
374
  title: "HTTPS to HTTP redirect",
375
375
  },
376
+ {
377
+ category: "redirects",
378
+ fix: "Deploy the redirect file your host reads (see Redirects in the deployment docs), or name the host in `deployment`.",
379
+ id: "BLUME_AUDIT_REDIRECT_NOT_SERVED",
380
+ severity: "error",
381
+ tier: "network",
382
+ title: "Live site doesn't redirect as configured",
383
+ },
376
384
 
377
385
  // Social
378
386
  {
@@ -570,7 +578,7 @@ export const CHECKS = [
570
578
  // Sitemap
571
579
  {
572
580
  category: "sitemap",
573
- fix: "Remove `draft`/`hidden`/`noindex` from the page's frontmatter if it should be indexed.",
581
+ fix: "Remove `draft`/`hidden`/`noindex` from the page's frontmatter if it should be indexed. A `public/sitemap.xml` replaces the generated sitemap, so add the page there or delete that file.",
574
582
  id: "BLUME_AUDIT_INDEXABLE_PAGE_NOT_IN_SITEMAP",
575
583
  severity: "warning",
576
584
  tier: "static",
@@ -666,6 +674,14 @@ export const CHECKS = [
666
674
  tier: "static",
667
675
  title: "robots.txt disallows a page that is in the sitemap",
668
676
  },
677
+ {
678
+ category: "robots",
679
+ fix: "Narrow or remove the rule in that crawler's `User-agent` group if it should read these pages. Blocking an AI crawler on purpose is fine; skip this check.",
680
+ id: "BLUME_AUDIT_ROBOTS_BLOCKS_CRAWLER",
681
+ severity: "warning",
682
+ tier: "static",
683
+ title: "robots.txt blocks one crawler from pages in the sitemap",
684
+ },
669
685
  {
670
686
  category: "robots",
671
687
  fix: "Set `deployment.site` so robots.txt can reference the sitemap.",
@@ -101,6 +101,12 @@ export const linkChecks: CheckModule = {
101
101
  return;
102
102
  }
103
103
  if (resolved.kind === "outside-base") {
104
+ // A full URL on this host outside the base names another app there
105
+ // (the navigation docs say to link one that way), not a page of this
106
+ // build that lost its base.
107
+ if (resolved.absolute) {
108
+ return;
109
+ }
104
110
  if (link.content) {
105
111
  found.push(
106
112
  finding(
@@ -215,11 +221,18 @@ export const linkChecks: CheckModule = {
215
221
 
216
222
  const homeUrl = normalizeBasePath(context.project.config.basePath) || "/";
217
223
  for (const page of orphanPages(context.pages, context.graph, homeUrl)) {
224
+ // A `sidebar.hidden` page is in no other page's navigation either, so
225
+ // it isn't reachable from the sidebar at all.
226
+ const fromNavigation = [
227
+ ...(context.graph.chromeIn.get(page.url) ?? []),
228
+ ].some((from) => from !== page.url);
218
229
  found.push(
219
230
  finding(
220
231
  "BLUME_AUDIT_ORPHAN_PAGE",
221
232
  pageSite(context, page),
222
- "No other page's body links here — it is reachable only from the sidebar."
233
+ fromNavigation
234
+ ? "No other page's body links here — it is reachable only from the sidebar."
235
+ : "No page links here, from its body or from navigation, so readers and crawlers can't reach it."
223
236
  )
224
237
  );
225
238
  }