blume 2.0.0 → 2.0.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 (454) hide show
  1. package/AGENTS.md +1 -1
  2. package/CHANGELOG.md +110 -0
  3. package/README.md +2 -2
  4. package/dist/cli/{chunk-f2972sbt.js → chunk-00gs3wqs.js} +1 -1
  5. package/dist/cli/{chunk-fa25z98p.js → chunk-273ygyr4.js} +4 -3
  6. package/dist/cli/chunk-273ygyr4.js.map +11 -0
  7. package/dist/cli/{chunk-pat2zzwc.js → chunk-3yce002v.js} +21 -8
  8. package/dist/cli/{chunk-pat2zzwc.js.map → chunk-3yce002v.js.map} +4 -4
  9. package/dist/cli/{chunk-d1tadaw7.js → chunk-4e9b9ra6.js} +17 -7
  10. package/dist/cli/chunk-4e9b9ra6.js.map +10 -0
  11. package/dist/cli/{chunk-zg2gtj10.js → chunk-5r8g91qn.js} +352 -676
  12. package/dist/cli/chunk-5r8g91qn.js.map +34 -0
  13. package/dist/cli/{chunk-11j0384y.js → chunk-6dtt0zfn.js} +14 -14
  14. package/dist/cli/{chunk-11j0384y.js.map → chunk-6dtt0zfn.js.map} +3 -3
  15. package/dist/cli/{chunk-bw22s759.js → chunk-6k4ftwze.js} +38 -19
  16. package/dist/cli/{chunk-bw22s759.js.map → chunk-6k4ftwze.js.map} +4 -4
  17. package/dist/cli/{chunk-sqn5t4q0.js → chunk-7mbqtmgb.js} +16 -7
  18. package/dist/cli/chunk-7mbqtmgb.js.map +10 -0
  19. package/dist/cli/{chunk-79njf86q.js → chunk-7vtckvaw.js} +21 -19
  20. package/dist/cli/chunk-7vtckvaw.js.map +11 -0
  21. package/dist/cli/{chunk-fh5hj5jt.js → chunk-8g8ytmgx.js} +17 -18
  22. package/dist/cli/{chunk-fh5hj5jt.js.map → chunk-8g8ytmgx.js.map} +2 -2
  23. package/dist/cli/{chunk-1w8dp3qb.js → chunk-91ws1n6j.js} +18 -15
  24. package/dist/cli/chunk-91ws1n6j.js.map +10 -0
  25. package/dist/cli/{chunk-7ez8ny0t.js → chunk-bbnwccaz.js} +2 -2
  26. package/dist/cli/{chunk-5a2z0198.js → chunk-bfwp9vp6.js} +18 -10
  27. package/dist/cli/chunk-bfwp9vp6.js.map +10 -0
  28. package/dist/cli/{chunk-ernrthtr.js → chunk-d1v5rhy0.js} +14 -15
  29. package/dist/cli/{chunk-ernrthtr.js.map → chunk-d1v5rhy0.js.map} +2 -2
  30. package/dist/cli/{chunk-a9kptbw5.js → chunk-ddndchfr.js} +21 -6
  31. package/dist/cli/chunk-ddndchfr.js.map +14 -0
  32. package/dist/cli/{chunk-zxccj738.js → chunk-esh98wmb.js} +1 -1
  33. package/dist/cli/{chunk-88cpgt6h.js → chunk-fsmrqk8a.js} +1 -1
  34. package/dist/cli/{chunk-b5aj94ah.js → chunk-g698a744.js} +5 -5
  35. package/dist/cli/{chunk-jts8mvcz.js → chunk-gs7r695n.js} +9 -3
  36. package/dist/cli/{chunk-jts8mvcz.js.map → chunk-gs7r695n.js.map} +3 -3
  37. package/dist/cli/{chunk-41za066z.js → chunk-h2ez8dzb.js} +4 -4
  38. package/dist/cli/{chunk-by2290sx.js → chunk-h7k3nq3v.js} +2 -2
  39. package/dist/cli/{chunk-j8mw0za6.js → chunk-hqp2ajnh.js} +252 -123
  40. package/dist/cli/chunk-hqp2ajnh.js.map +35 -0
  41. package/dist/cli/{chunk-mnqj32sj.js → chunk-hr8ne106.js} +119 -50
  42. package/dist/cli/chunk-hr8ne106.js.map +13 -0
  43. package/dist/cli/{chunk-mwt1k8n7.js → chunk-j85scx15.js} +74 -30
  44. package/dist/cli/chunk-j85scx15.js.map +10 -0
  45. package/dist/cli/{chunk-z01ze5c1.js → chunk-k7pj68a8.js} +63 -22
  46. package/dist/cli/chunk-k7pj68a8.js.map +11 -0
  47. package/dist/cli/{chunk-2q1dwty4.js → chunk-mqc662a6.js} +8 -8
  48. package/dist/cli/{chunk-2q1dwty4.js.map → chunk-mqc662a6.js.map} +4 -4
  49. package/dist/cli/{chunk-y3e45rc8.js → chunk-n1yg3tj3.js} +4 -4
  50. package/dist/cli/{chunk-bctazmbk.js → chunk-nfcyttvj.js} +17 -6
  51. package/dist/cli/chunk-nfcyttvj.js.map +10 -0
  52. package/dist/cli/{chunk-6k8vp3ta.js → chunk-pbg5a4s3.js} +60 -28
  53. package/dist/cli/chunk-pbg5a4s3.js.map +19 -0
  54. package/dist/cli/{chunk-d80hr03s.js → chunk-ppzjqwx2.js} +24 -17
  55. package/dist/cli/{chunk-d80hr03s.js.map → chunk-ppzjqwx2.js.map} +4 -4
  56. package/dist/cli/{chunk-beat36xx.js → chunk-qkb5a8sa.js} +25 -10
  57. package/dist/cli/chunk-qkb5a8sa.js.map +10 -0
  58. package/dist/cli/{chunk-bnbmcwfb.js → chunk-sqw4ekg1.js} +5 -5
  59. package/dist/cli/{chunk-bnbmcwfb.js.map → chunk-sqw4ekg1.js.map} +3 -3
  60. package/dist/cli/{chunk-xaz13gwg.js → chunk-v6ya5kcb.js} +3268 -1150
  61. package/dist/cli/chunk-v6ya5kcb.js.map +189 -0
  62. package/dist/cli/{chunk-tzne8qfq.js → chunk-w4bxdvsa.js} +18 -15
  63. package/dist/cli/chunk-w4bxdvsa.js.map +10 -0
  64. package/dist/cli/{chunk-f7t03s3g.js → chunk-wdrt2k2v.js} +2 -2
  65. package/dist/cli/{chunk-z1f5arsg.js → chunk-xh43dwgw.js} +170 -55
  66. package/dist/cli/chunk-xh43dwgw.js.map +36 -0
  67. package/dist/cli/{chunk-pnnvybbk.js → chunk-zp79m0ts.js} +5 -5
  68. package/dist/cli/{chunk-pnnvybbk.js.map → chunk-zp79m0ts.js.map} +2 -2
  69. package/dist/cli/index.js +160 -35
  70. package/dist/cli/index.js.map +4 -4
  71. package/dist/types/ai/agent-readability.d.ts +1 -1
  72. package/dist/types/ai/agent-surface.d.ts +32 -0
  73. package/dist/types/ai/api/paths.d.ts +1 -1
  74. package/dist/types/ai/api-catalog.d.ts +7 -1
  75. package/dist/types/ai/ask-context.d.ts +14 -7
  76. package/dist/types/ai/ask.d.ts +43 -43
  77. package/dist/types/ai/component-markdown.d.ts +4 -4
  78. package/dist/types/ai/index.d.ts +3 -3
  79. package/dist/types/ai/openapi-components.d.ts +1 -1
  80. package/dist/types/ai/relative-links.d.ts +9 -4
  81. package/dist/types/ai/serializers.d.ts +1 -1
  82. package/dist/types/ai/static-expression.d.ts +28 -0
  83. package/dist/types/ai/visibility.d.ts +1 -1
  84. package/dist/types/analytics/databuddy.d.ts +43 -0
  85. package/dist/types/analytics/index.d.ts +2 -0
  86. package/dist/types/analytics/schema.d.ts +14 -0
  87. package/dist/types/cli/env.d.ts +5 -0
  88. package/dist/types/cli/init/scaffold.d.ts +19 -3
  89. package/dist/types/cli/init/starter-spec.d.ts +11 -0
  90. package/dist/types/core/base-path.d.ts +9 -0
  91. package/dist/types/core/config-input.d.ts +21 -21
  92. package/dist/types/core/config.d.ts +5 -5
  93. package/dist/types/core/data.d.ts +3 -3
  94. package/dist/types/core/graph.d.ts +2 -0
  95. package/dist/types/core/i18n-ui.d.ts +37 -8
  96. package/dist/types/core/i18n.d.ts +7 -1
  97. package/dist/types/core/links.d.ts +3 -1
  98. package/dist/types/core/load-module.d.ts +10 -0
  99. package/dist/types/core/locale-links.d.ts +12 -2
  100. package/dist/types/core/meta.d.ts +5 -1
  101. package/dist/types/core/nav-diagnostics.d.ts +10 -0
  102. package/dist/types/core/navigation.d.ts +38 -0
  103. package/dist/types/core/ordering-prefix.d.ts +4 -0
  104. package/dist/types/core/safe-links.d.ts +3 -1
  105. package/dist/types/core/schema.d.ts +57 -18
  106. package/dist/types/core/sources/github-releases.d.ts +5 -0
  107. package/dist/types/core/sources/lower.d.ts +14 -2
  108. package/dist/types/core/sources/normalize.d.ts +10 -1
  109. package/dist/types/core/sources/remote.d.ts +11 -1
  110. package/dist/types/core/sources/resolve.d.ts +12 -0
  111. package/dist/types/core/sources/types.d.ts +31 -0
  112. package/dist/types/core/types.d.ts +9 -0
  113. package/dist/types/core/unrecognized-keys.d.ts +1 -1
  114. package/dist/types/deploy/adapters/node.d.ts +5 -2
  115. package/dist/types/deploy/adapters/types.d.ts +7 -0
  116. package/dist/types/deploy/cloudflare-negotiation.d.ts +3 -2
  117. package/dist/types/deploy/headers.d.ts +31 -7
  118. package/dist/types/deploy/node-headers.d.ts +43 -8
  119. package/dist/types/deploy/platforms/netlify.d.ts +27 -2
  120. package/dist/types/deploy/platforms/node.d.ts +6 -5
  121. package/dist/types/deploy/platforms/types.d.ts +7 -0
  122. package/dist/types/deploy/platforms/vercel.d.ts +3 -2
  123. package/dist/types/deploy/redirects.d.ts +7 -2
  124. package/dist/types/deploy/vercel-negotiation.d.ts +3 -2
  125. package/dist/types/openapi/model.d.ts +20 -6
  126. package/dist/types/search/documents.d.ts +1 -1
  127. package/dist/types/search/orama-index.d.ts +1 -1
  128. package/dist/types/search/sync/algolia.d.ts +3 -1
  129. package/docs/01-quickstart.mdx +3 -2
  130. package/docs/02-deployment.mdx +21 -12
  131. package/docs/03-upgrading.mdx +22 -9
  132. package/docs/04-migrating.mdx +4 -4
  133. package/docs/08-faq.mdx +12 -5
  134. package/docs/advanced/changelog.mdx +1 -1
  135. package/docs/advanced/custom-pages.mdx +5 -3
  136. package/docs/advanced/skills.mdx +1 -1
  137. package/docs/cli/audit.mdx +5 -5
  138. package/docs/cli/doctor.mdx +4 -4
  139. package/docs/cli/evals.mdx +9 -9
  140. package/docs/cli/index.mdx +6 -3
  141. package/docs/cli/translate.mdx +8 -8
  142. package/docs/configuration/analytics.mdx +23 -2
  143. package/docs/configuration/{ask-ai.mdx → assistant.mdx} +20 -20
  144. package/docs/configuration/customization.mdx +5 -4
  145. package/docs/configuration/index.mdx +7 -5
  146. package/docs/configuration/meta.ts +1 -1
  147. package/docs/configuration/search.mdx +2 -2
  148. package/docs/content/components.mdx +1 -1
  149. package/docs/content/frontmatter.mdx +5 -1
  150. package/docs/content/i18n.mdx +3 -3
  151. package/docs/content/index.mdx +4 -2
  152. package/docs/content/islands.mdx +1 -1
  153. package/docs/content/meta.mdx +5 -3
  154. package/docs/content/navigation.mdx +29 -4
  155. package/docs/content/sources.mdx +18 -12
  156. package/docs/content/versioning.mdx +1 -0
  157. package/docs/discoverability/agent-discovery.mdx +22 -8
  158. package/docs/discoverability/index.mdx +3 -3
  159. package/docs/discoverability/llms-txt.mdx +2 -5
  160. package/docs/discoverability/markdown.mdx +5 -3
  161. package/docs/discoverability/mcp.mdx +4 -0
  162. package/docs/discoverability/metadata.mdx +3 -2
  163. package/docs/discoverability/open-graph.mdx +6 -4
  164. package/docs/discoverability/rss.mdx +3 -1
  165. package/docs/index.mdx +2 -2
  166. package/docs/references/asyncapi.mdx +1 -1
  167. package/docs/references/graphql.mdx +2 -2
  168. package/docs/references/openapi.mdx +3 -3
  169. package/package.json +1 -1
  170. package/skills/blume/SKILL.md +5 -5
  171. package/skills/blume-migrate/SKILL.md +6 -6
  172. package/skills/blume-migrate/references/docusaurus.md +6 -6
  173. package/skills/blume-migrate/references/mintlify.md +3 -3
  174. package/skills/blume-migrate/references/monorepo.md +1 -1
  175. package/skills/blume-migrate/references/nextra.md +1 -1
  176. package/skills/blume-migrate/references/starlight.md +5 -5
  177. package/skills/blume-migrate/scripts/mintlify-codemod.mjs +2 -2
  178. package/skills/blume-update-docs/references/audit-checklist.md +1 -1
  179. package/src/ai/agent-readability.ts +4 -4
  180. package/src/ai/agent-surface.ts +56 -0
  181. package/src/ai/api/paths.ts +1 -1
  182. package/src/ai/api/spec.ts +15 -2
  183. package/src/ai/api-catalog.ts +7 -1
  184. package/src/ai/ask-context.ts +21 -9
  185. package/src/ai/ask-data.ts +28 -14
  186. package/src/ai/ask.ts +84 -71
  187. package/src/ai/component-markdown.ts +43 -37
  188. package/src/ai/cors.ts +3 -3
  189. package/src/ai/index.ts +16 -16
  190. package/src/ai/link-headers.ts +8 -3
  191. package/src/ai/llms.ts +4 -3
  192. package/src/ai/markdown.ts +23 -11
  193. package/src/ai/mcp/server.ts +78 -7
  194. package/src/ai/openapi-components.ts +1 -1
  195. package/src/ai/relative-links.ts +78 -10
  196. package/src/ai/serializers.ts +1 -1
  197. package/src/ai/static-expression.ts +416 -0
  198. package/src/ai/visibility.ts +46 -15
  199. package/src/analytics/databuddy.ts +67 -0
  200. package/src/analytics/head.ts +4 -0
  201. package/src/analytics/index.ts +2 -0
  202. package/src/analytics/posthog.ts +21 -3
  203. package/src/analytics/schema.ts +2 -0
  204. package/src/astro/generate.ts +90 -45
  205. package/src/astro/module-types.ts +1 -1
  206. package/src/astro/runtime-deps.ts +6 -6
  207. package/src/astro/templates.ts +87 -49
  208. package/src/audit/catalog.ts +2 -2
  209. package/src/audit/checks/assets.ts +25 -5
  210. package/src/audit/checks/content.ts +20 -2
  211. package/src/audit/checks/i18n.ts +46 -8
  212. package/src/audit/checks/indexability.ts +39 -19
  213. package/src/audit/checks/links.ts +14 -0
  214. package/src/audit/checks/llms.ts +6 -3
  215. package/src/audit/checks/network.ts +3 -1
  216. package/src/audit/checks/og-image.ts +10 -0
  217. package/src/audit/checks/robots.ts +6 -1
  218. package/src/audit/checks/sitemap.ts +56 -31
  219. package/src/audit/checks/social.ts +21 -2
  220. package/src/audit/crawl.ts +88 -15
  221. package/src/audit/report.ts +54 -17
  222. package/src/audit/run.ts +1 -0
  223. package/src/audit/types.ts +18 -0
  224. package/src/audit/url.ts +37 -6
  225. package/src/blume-modules.d.ts +2 -2
  226. package/src/cli/build-failure.ts +50 -0
  227. package/src/cli/commands/audit.ts +8 -1
  228. package/src/cli/commands/build.ts +19 -5
  229. package/src/cli/commands/check.ts +2 -0
  230. package/src/cli/commands/doctor.ts +7 -5
  231. package/src/cli/commands/eval.ts +4 -10
  232. package/src/cli/commands/init.ts +15 -5
  233. package/src/cli/commands/migrate.ts +2 -2
  234. package/src/cli/commands/preview.ts +15 -0
  235. package/src/cli/commands/sync.ts +2 -0
  236. package/src/cli/commands/translate.ts +12 -9
  237. package/src/cli/commands/upgrade.ts +13 -2
  238. package/src/cli/eject-scripts.ts +32 -7
  239. package/src/cli/env.ts +12 -1
  240. package/src/cli/init/scaffold.ts +117 -11
  241. package/src/cli/init/starter-spec.ts +235 -0
  242. package/src/cli/required-secrets.ts +3 -3
  243. package/src/components/content/AccordionItem.astro +26 -22
  244. package/src/components/content/Badge.astro +2 -9
  245. package/src/components/content/Card.astro +2 -2
  246. package/src/components/content/Component.astro +9 -3
  247. package/src/components/content/Expandable.astro +5 -1
  248. package/src/components/content/Frame.astro +2 -7
  249. package/src/components/content/GithubInfo.astro +12 -2
  250. package/src/components/content/Prompt.astro +2 -7
  251. package/src/components/content/Tabs.astro +54 -5
  252. package/src/components/content/Tile.astro +1 -1
  253. package/src/components/content/Tooltip.astro +69 -7
  254. package/src/components/content/Tree.astro +7 -2
  255. package/src/components/content/TypeTable.astro +10 -5
  256. package/src/components/content/Update.astro +8 -2
  257. package/src/components/content/auto-type-table.ts +4 -1
  258. package/src/components/content/badge-color.ts +17 -0
  259. package/src/components/content/base-href.ts +18 -3
  260. package/src/components/content/inline-markdown.ts +27 -7
  261. package/src/components/copy-feedback.ts +36 -9
  262. package/src/components/islands/{AskAI.astro → Assistant.astro} +10 -10
  263. package/src/components/islands/{ask-ai.tsx → assistant.tsx} +39 -25
  264. package/src/components/islands/hooks.ts +21 -14
  265. package/src/components/islands/webmcp.ts +12 -8
  266. package/src/components/layout/Banner.astro +23 -4
  267. package/src/components/layout/Header.astro +32 -19
  268. package/src/components/layout/Logo.astro +5 -0
  269. package/src/components/layout/NavSelector.astro +6 -1
  270. package/src/components/layout/NavTabMenu.astro +133 -0
  271. package/src/components/layout/NavTree.astro +15 -6
  272. package/src/components/layout/NavTreeCache.astro +5 -2
  273. package/src/components/layout/NavTreeScript.astro +45 -5
  274. package/src/components/layout/PageLayout.astro +28 -14
  275. package/src/components/layout/Pagination.astro +7 -7
  276. package/src/components/layout/ReferenceLayout.astro +5 -2
  277. package/src/components/layout/RootLayout.astro +27 -14
  278. package/src/components/layout/Search.astro +18 -14
  279. package/src/components/layout/analytics-client.ts +3 -1
  280. package/src/components/layout/drawer-inert.ts +1 -1
  281. package/src/components/openapi/GraphqlType.astro +11 -3
  282. package/src/components/openapi/MessageComposer.astro +1 -1
  283. package/src/components/openapi/Operation.astro +21 -5
  284. package/src/components/openapi/PanelTabs.astro +4 -1
  285. package/src/components/openapi/Playground.astro +8 -2
  286. package/src/components/openapi/RequestPanel.astro +9 -5
  287. package/src/components/openapi/SchemaProperty.astro +11 -33
  288. package/src/components/openapi/SchemaTable.astro +27 -52
  289. package/src/components/openapi/description.ts +2 -2
  290. package/src/components/openapi/helpers.ts +90 -12
  291. package/src/components/openapi/message-composer.ts +8 -0
  292. package/src/components/openapi/message-model.ts +12 -2
  293. package/src/components/openapi/message.ts +4 -1
  294. package/src/components/openapi/operation-model.ts +40 -8
  295. package/src/components/openapi/panel.ts +29 -5
  296. package/src/components/openapi/playground-client.ts +37 -7
  297. package/src/components/openapi/playground-schema.ts +25 -6
  298. package/src/components/openapi/request.ts +2 -0
  299. package/src/components/openapi/schema-tree.ts +203 -0
  300. package/src/components/openapi/snippets.ts +30 -15
  301. package/src/components/openapi/validate-json.ts +1 -1
  302. package/src/core/base-path.ts +15 -0
  303. package/src/core/code-fences.ts +1 -1
  304. package/src/core/config-input.ts +23 -23
  305. package/src/core/config.ts +21 -7
  306. package/src/core/data.ts +3 -3
  307. package/src/core/diagnostics.ts +217 -30
  308. package/src/core/graph.ts +120 -11
  309. package/src/core/i18n-ui.ts +74 -11
  310. package/src/core/i18n.ts +13 -2
  311. package/src/core/links.ts +7 -4
  312. package/src/core/load-module.ts +20 -0
  313. package/src/core/locale-links.ts +17 -17
  314. package/src/core/manifest.ts +3 -2
  315. package/src/core/meta.ts +69 -6
  316. package/src/core/nav-diagnostics.ts +56 -1
  317. package/src/core/navigation.ts +245 -76
  318. package/src/core/ordering-prefix.ts +27 -0
  319. package/src/core/project-graph.ts +23 -1
  320. package/src/core/request-body.ts +1 -1
  321. package/src/core/safe-href.ts +53 -1
  322. package/src/core/safe-links.ts +11 -2
  323. package/src/core/schema.ts +95 -34
  324. package/src/core/server-features.ts +2 -2
  325. package/src/core/sources/assets.ts +83 -41
  326. package/src/core/sources/contentful-rich-text.ts +3 -2
  327. package/src/core/sources/contentful.ts +25 -12
  328. package/src/core/sources/filesystem.ts +5 -1
  329. package/src/core/sources/github-releases.ts +106 -5
  330. package/src/core/sources/lexical.ts +9 -4
  331. package/src/core/sources/lower.ts +79 -26
  332. package/src/core/sources/mdx-remote.ts +1 -0
  333. package/src/core/sources/normalize.ts +132 -30
  334. package/src/core/sources/notion.ts +26 -10
  335. package/src/core/sources/obsidian.ts +17 -3
  336. package/src/core/sources/payload.ts +1 -0
  337. package/src/core/sources/portable-text.ts +63 -44
  338. package/src/core/sources/remote.ts +18 -2
  339. package/src/core/sources/resolve.ts +52 -33
  340. package/src/core/sources/sanity.ts +1 -0
  341. package/src/core/sources/strapi-blocks.ts +9 -4
  342. package/src/core/sources/strapi.ts +1 -0
  343. package/src/core/sources/types.ts +31 -0
  344. package/src/core/types.ts +9 -0
  345. package/src/core/ui-packs/ar.ts +17 -5
  346. package/src/core/ui-packs/bg.ts +17 -5
  347. package/src/core/ui-packs/bn.ts +17 -5
  348. package/src/core/ui-packs/ca.ts +17 -5
  349. package/src/core/ui-packs/cs.ts +17 -5
  350. package/src/core/ui-packs/da.ts +17 -5
  351. package/src/core/ui-packs/de.ts +17 -5
  352. package/src/core/ui-packs/el.ts +17 -5
  353. package/src/core/ui-packs/es.ts +17 -5
  354. package/src/core/ui-packs/fa.ts +17 -5
  355. package/src/core/ui-packs/fi.ts +17 -5
  356. package/src/core/ui-packs/fr.ts +17 -5
  357. package/src/core/ui-packs/he.ts +17 -5
  358. package/src/core/ui-packs/hi.ts +17 -5
  359. package/src/core/ui-packs/hr.ts +17 -5
  360. package/src/core/ui-packs/hu.ts +17 -5
  361. package/src/core/ui-packs/id.ts +17 -5
  362. package/src/core/ui-packs/it.ts +17 -5
  363. package/src/core/ui-packs/ja.ts +17 -5
  364. package/src/core/ui-packs/ko.ts +17 -5
  365. package/src/core/ui-packs/nl.ts +17 -5
  366. package/src/core/ui-packs/no.ts +17 -5
  367. package/src/core/ui-packs/pl.ts +17 -5
  368. package/src/core/ui-packs/pt-br.ts +17 -5
  369. package/src/core/ui-packs/pt.ts +17 -5
  370. package/src/core/ui-packs/ro.ts +17 -5
  371. package/src/core/ui-packs/ru.ts +17 -5
  372. package/src/core/ui-packs/sk.ts +17 -5
  373. package/src/core/ui-packs/sr.ts +17 -5
  374. package/src/core/ui-packs/sv.ts +17 -5
  375. package/src/core/ui-packs/th.ts +17 -5
  376. package/src/core/ui-packs/tr.ts +17 -5
  377. package/src/core/ui-packs/uk.ts +17 -5
  378. package/src/core/ui-packs/vi.ts +17 -5
  379. package/src/core/ui-packs/zh-tw.ts +17 -5
  380. package/src/core/ui-packs/zh.ts +17 -5
  381. package/src/core/unrecognized-keys.ts +1 -1
  382. package/src/core/version-cut.ts +17 -2
  383. package/src/core/versions.ts +4 -1
  384. package/src/deploy/adapters/node.ts +5 -2
  385. package/src/deploy/adapters/registry.ts +2 -1
  386. package/src/deploy/adapters/types.ts +13 -1
  387. package/src/deploy/artifacts.ts +25 -5
  388. package/src/deploy/cloudflare-negotiation.ts +23 -4
  389. package/src/deploy/headers.ts +67 -51
  390. package/src/deploy/node-headers.ts +148 -27
  391. package/src/deploy/platforms/cloudflare.ts +1 -0
  392. package/src/deploy/platforms/netlify.ts +82 -5
  393. package/src/deploy/platforms/node.ts +7 -5
  394. package/src/deploy/platforms/static.ts +1 -0
  395. package/src/deploy/platforms/types.ts +7 -0
  396. package/src/deploy/platforms/vercel.ts +9 -3
  397. package/src/deploy/redirects.ts +14 -3
  398. package/src/deploy/vercel-negotiation.ts +35 -3
  399. package/src/eval/agents.ts +10 -2
  400. package/src/eval/run.ts +25 -0
  401. package/src/markdown/base-links.ts +55 -8
  402. package/src/markdown/index.ts +6 -3
  403. package/src/markdown/relative-links.ts +3 -23
  404. package/src/markdown/route-snapshot.ts +37 -0
  405. package/src/og/card.ts +20 -4
  406. package/src/og/derive.ts +145 -4
  407. package/src/openapi/graphql-build.ts +28 -2
  408. package/src/openapi/model.ts +77 -13
  409. package/src/openapi/render-mdx.ts +10 -1
  410. package/src/registry/eject.ts +135 -37
  411. package/src/search/documents.ts +24 -3
  412. package/src/search/orama-index.ts +1 -1
  413. package/src/search/sync/algolia.ts +36 -2
  414. package/src/sources/registry.ts +5 -0
  415. package/src/theme/entry.ts +11 -4
  416. package/src/translate/agents.ts +6 -1
  417. package/src/translate/ledger.ts +26 -3
  418. package/src/translate/meta.ts +11 -3
  419. package/src/translate/report.ts +1 -1
  420. package/src/translate/run.ts +11 -5
  421. package/src/translate/validate.ts +10 -1
  422. package/src/translate/work-list.ts +36 -3
  423. package/src/upgrade/upgrade.ts +37 -5
  424. package/dist/cli/chunk-1w8dp3qb.js.map +0 -10
  425. package/dist/cli/chunk-5a2z0198.js.map +0 -10
  426. package/dist/cli/chunk-6k8vp3ta.js.map +0 -19
  427. package/dist/cli/chunk-79njf86q.js.map +0 -11
  428. package/dist/cli/chunk-a9kptbw5.js.map +0 -14
  429. package/dist/cli/chunk-abh8yjkn.js +0 -31
  430. package/dist/cli/chunk-abh8yjkn.js.map +0 -10
  431. package/dist/cli/chunk-bctazmbk.js.map +0 -10
  432. package/dist/cli/chunk-beat36xx.js.map +0 -10
  433. package/dist/cli/chunk-d1tadaw7.js.map +0 -10
  434. package/dist/cli/chunk-fa25z98p.js.map +0 -11
  435. package/dist/cli/chunk-j8mw0za6.js.map +0 -35
  436. package/dist/cli/chunk-mnqj32sj.js.map +0 -12
  437. package/dist/cli/chunk-mwt1k8n7.js.map +0 -10
  438. package/dist/cli/chunk-nk3ts2xk.js +0 -51
  439. package/dist/cli/chunk-nk3ts2xk.js.map +0 -10
  440. package/dist/cli/chunk-sqn5t4q0.js.map +0 -10
  441. package/dist/cli/chunk-tzne8qfq.js.map +0 -10
  442. package/dist/cli/chunk-xaz13gwg.js.map +0 -182
  443. package/dist/cli/chunk-z01ze5c1.js.map +0 -10
  444. package/dist/cli/chunk-z1f5arsg.js.map +0 -36
  445. package/dist/cli/chunk-zg2gtj10.js.map +0 -35
  446. /package/dist/cli/{chunk-f2972sbt.js.map → chunk-00gs3wqs.js.map} +0 -0
  447. /package/dist/cli/{chunk-7ez8ny0t.js.map → chunk-bbnwccaz.js.map} +0 -0
  448. /package/dist/cli/{chunk-zxccj738.js.map → chunk-esh98wmb.js.map} +0 -0
  449. /package/dist/cli/{chunk-88cpgt6h.js.map → chunk-fsmrqk8a.js.map} +0 -0
  450. /package/dist/cli/{chunk-b5aj94ah.js.map → chunk-g698a744.js.map} +0 -0
  451. /package/dist/cli/{chunk-41za066z.js.map → chunk-h2ez8dzb.js.map} +0 -0
  452. /package/dist/cli/{chunk-by2290sx.js.map → chunk-h7k3nq3v.js.map} +0 -0
  453. /package/dist/cli/{chunk-y3e45rc8.js.map → chunk-n1yg3tj3.js.map} +0 -0
  454. /package/dist/cli/{chunk-f7t03s3g.js.map → chunk-wdrt2k2v.js.map} +0 -0
@@ -23,6 +23,10 @@ import {
23
23
  detectNeedsReact,
24
24
  detectUsesMath,
25
25
  languageIconCssFor,
26
+ PLAYGROUND_PROXY_ENTRY,
27
+ playgroundProxyPattern,
28
+ proxyAllowlistWarnings,
29
+ specOrigins,
26
30
  } from "../astro/generate.ts";
27
31
  import { discoverIslands } from "../astro/islands.ts";
28
32
  import { customOgRoutes, discoverPages, routeIsTaken } from "../astro/pages.ts";
@@ -32,6 +36,7 @@ import {
32
36
  astroConfigTemplate,
33
37
  catchAllPageTemplate,
34
38
  changelogIndexTemplate,
39
+ contentAssetsEndpointTemplate,
35
40
  contentConfigTemplate,
36
41
  exampleMapTemplate,
37
42
  exampleSlug,
@@ -43,6 +48,7 @@ import {
43
48
  mixedbreadSearchEndpointTemplate,
44
49
  notFoundPageTemplate,
45
50
  ogEndpointTemplate,
51
+ playgroundProxyTemplate,
46
52
  rawMarkdownEndpointTemplate,
47
53
  rssEndpointTemplate,
48
54
  runtimeDependencies,
@@ -51,6 +57,7 @@ import {
51
57
  searchEndpointTemplate,
52
58
  staticJsonEndpointTemplate,
53
59
  } from "../astro/templates.ts";
60
+ import { collectContentAssets } from "../core/content-assets.ts";
54
61
  import { buildIncludeGraph } from "../core/includes.ts";
55
62
  import { packageRoot } from "../core/package-root.ts";
56
63
  import { scanProject } from "../core/project-graph.ts";
@@ -62,7 +69,10 @@ import {
62
69
  import type { ProjectContext } from "../core/types.ts";
63
70
  import { buildRssFeeds, renderRssFeed } from "../deploy/rss.ts";
64
71
  import type { OpenApiData } from "../openapi/model.ts";
65
- import { hasScalarReferences } from "../openapi/references.ts";
72
+ import {
73
+ hasScalarReferences,
74
+ needsPlaygroundProxy,
75
+ } from "../openapi/references.ts";
66
76
  import { buildReferenceFiles } from "../openapi/scalar.ts";
67
77
  import { isOpenApiSource } from "../openapi/source.ts";
68
78
  import { buildSearchDocuments } from "../search/documents.ts";
@@ -116,16 +126,16 @@ const ejectOpenApiData = (project: BlumeProject): OpenApiData => {
116
126
  };
117
127
 
118
128
  /**
119
- * The Ask AI endpoint plus, unless the backend runs its own retrieval (Inkeep),
120
- * its grounding snapshot. Empty when Ask AI is disabled.
129
+ * The assistant endpoint plus, unless the backend runs its own retrieval (Inkeep),
130
+ * its grounding snapshot. Empty when the assistant is disabled.
121
131
  */
122
132
  const askFiles = async (
123
133
  project: BlumeProject,
124
134
  srcDir: string,
125
135
  genDir: string
126
136
  ): Promise<{ content: string; path: string }[]> => {
127
- const { ask } = project.config.ai;
128
- if (!(ask?.enabled && !ask.endpoint)) {
137
+ const { assistant } = project.config.ai;
138
+ if (!(assistant?.enabled && !assistant.endpoint)) {
129
139
  const endpointPath = join(srcDir, "pages", "api", "ask.ts");
130
140
  if (existsSync(endpointPath)) {
131
141
  const content = await readFile(endpointPath, "utf-8");
@@ -136,13 +146,13 @@ const askFiles = async (
136
146
  await rm(join(genDir, "ask-data.json"), { force: true });
137
147
  return [];
138
148
  }
139
- const backend = resolveAskBackend(ask.provider);
149
+ const backend = resolveAskBackend(assistant.provider);
140
150
  const files = [
141
151
  {
142
152
  content: askEndpointTemplate(backend, {
143
- cors: ask.cors,
144
- instructions: ask.instructions,
145
- retrieval: ask.retrieval,
153
+ cors: assistant.cors,
154
+ instructions: assistant.instructions,
155
+ retrieval: assistant.retrieval,
146
156
  }),
147
157
  path: join(srcDir, "pages", "api", "ask.ts"),
148
158
  },
@@ -190,6 +200,43 @@ const mcpDiscoveryPages = (
190
200
  ]
191
201
  : [];
192
202
 
203
+ /**
204
+ * The playground's built-in CORS proxy route (`playground: { proxy: true }`),
205
+ * mirroring `planPlaygroundProxy` in generate.ts: injected like the MCP
206
+ * discovery routes, because Astro treats a `_`-prefixed page file as private,
207
+ * and mounted under `basePath` at the URL the playground sends to. Empty when
208
+ * no reference routes its playground through it.
209
+ */
210
+ const playgroundProxyPages = (
211
+ config: BlumeProject["config"]
212
+ ): { entrypoint: string; pattern: string }[] =>
213
+ needsPlaygroundProxy(config)
214
+ ? [
215
+ {
216
+ entrypoint: toPosix(join("src", PLAYGROUND_PROXY_ENTRY)),
217
+ pattern: playgroundProxyPattern(config),
218
+ },
219
+ ]
220
+ : [];
221
+
222
+ /**
223
+ * The proxy endpoint behind {@link playgroundProxyPages}, with the origin
224
+ * allowlist baked in from the same parsed specs the reference pages render.
225
+ */
226
+ const playgroundProxyFiles = (
227
+ config: BlumeProject["config"],
228
+ openApiData: OpenApiData,
229
+ srcDir: string
230
+ ): { content: string; path: string }[] =>
231
+ needsPlaygroundProxy(config)
232
+ ? [
233
+ {
234
+ content: playgroundProxyTemplate(specOrigins(openApiData)),
235
+ path: join(srcDir, PLAYGROUND_PROXY_ENTRY),
236
+ },
237
+ ]
238
+ : [];
239
+
193
240
  /**
194
241
  * The MCP data snapshot, server endpoint, and `.well-known` discovery
195
242
  * documents, mirroring `writeMcpFiles` in generate.ts. Empty when the server
@@ -265,6 +312,40 @@ const changelogFiles = (
265
312
  ];
266
313
  };
267
314
 
315
+ /**
316
+ * The colocated-image map and the `/blume-assets/[...asset]` endpoint that
317
+ * serves it, mirroring generate.ts: the ejected raw Markdown points
318
+ * `![x](./diagram.png)` at `/blume-assets/content/…`, and the config aliases
319
+ * `blume:content-assets` to the map. Each original's path is written relative
320
+ * to the project root (the directory `astro dev`/`astro build` run from), so
321
+ * the ejected app builds from any checkout. Remote-source images need no
322
+ * endpoint: eject copies them into the project's own `public/blume-assets`.
323
+ */
324
+ const contentAssetFiles = async (
325
+ project: BlumeProject,
326
+ root: string,
327
+ srcDir: string,
328
+ genDir: string
329
+ ): Promise<{ content: string; path: string }[]> => {
330
+ const assets = await collectContentAssets(project);
331
+ const portable = Object.fromEntries(
332
+ Object.entries(assets).map(([param, abs]) => [
333
+ param,
334
+ toPosix(relative(root, abs)),
335
+ ])
336
+ );
337
+ return [
338
+ {
339
+ content: `${JSON.stringify(portable)}\n`,
340
+ path: join(genDir, "content-assets.json"),
341
+ },
342
+ {
343
+ content: contentAssetsEndpointTemplate(null),
344
+ path: join(srcDir, "pages", "blume-assets", "[...asset].ts"),
345
+ },
346
+ ];
347
+ };
348
+
268
349
  /** Contents of the configured `examples.css`, or `""` when unset/absent. */
269
350
  /**
270
351
  * Read a user stylesheet that eject inlines into a generated entry under
@@ -309,29 +390,33 @@ const examplesPreviewFiles = (
309
390
 
310
391
  /**
311
392
  * The packages the ejected app imports by bare name: Astro, Tailwind's Vite
312
- * plugin, `blume` itself, the integrations the config wires in, whatever the
313
- * configured adapters declare, and React when islands, examples, or Ask AI
314
- * render with it (Blume ships React, so projects rarely list it). They have
315
- * to be the project's own dependencies after eject — under a strict linker
316
- * such as pnpm nothing else makes them resolvable, and `astro build` fails.
317
- * The hidden runtime reaches the same packages through its `node_modules`
318
- * junction into Blume's own, so only eject declares the ones its generated
319
- * files import directly: the AI SDK the Ask AI route streams through (unless
320
- * `ai.ask.endpoint` points elsewhere, when no route is written) and the EPUB
321
- * generator's browser bundle `features.ts` loads.
393
+ * plugin, Tailwind and its typography plugin (which the generated `app.css`
394
+ * and `examples.css` import by name), `blume` itself, the integrations the
395
+ * config wires in, whatever the configured adapters declare, and React when
396
+ * islands, examples, or the assistant render with it (Blume ships React, so
397
+ * projects rarely list it). They have to be the project's own dependencies
398
+ * after eject — under a strict linker such as pnpm nothing else makes them
399
+ * resolvable, and `astro build` fails. The hidden runtime reaches the same
400
+ * packages through its `node_modules` junction into Blume's own, so only
401
+ * eject declares the ones its generated files import directly: the AI SDK the
402
+ * assistant route streams through (unless `ai.assistant.endpoint` points
403
+ * elsewhere, when no route is written) and the EPUB generator's browser bundle
404
+ * `features.ts` loads (only named there when `export.epub` is on).
322
405
  */
323
406
  const ejectDependencies = (
324
407
  options: Parameters<typeof runtimeDependencies>[0]
325
408
  ): string[] => {
326
- const { ask } = options.config.ai;
409
+ const { assistant } = options.config.ai;
327
410
  return [
328
411
  ...new Set([
329
412
  "astro",
330
413
  "@tailwindcss/vite",
414
+ "tailwindcss",
415
+ "@tailwindcss/typography",
331
416
  "blume",
332
417
  ...runtimeDependencies(options),
333
418
  ...(options.needsReact ? ["react", "react-dom"] : []),
334
- ...(ask?.enabled && !ask.endpoint ? ["ai"] : []),
419
+ ...(assistant?.enabled && !assistant.endpoint ? ["ai"] : []),
335
420
  ...(options.config.export.epub ? ["epub-gen-memory"] : []),
336
421
  ]),
337
422
  ];
@@ -364,7 +449,7 @@ export const eject = async (
364
449
 
365
450
  const srcDir = join(root, "src");
366
451
  const genDir = join(srcDir, "generated");
367
- const askEnabled = config.ai.ask?.enabled ?? false;
452
+ const assistantEnabled = config.ai.assistant?.enabled ?? false;
368
453
  const exportPdf = config.export.pdf;
369
454
  const exportEpub = config.export.epub;
370
455
 
@@ -402,13 +487,14 @@ export const eject = async (
402
487
  relativeTo: genDir,
403
488
  });
404
489
  // Island/example/override frameworks drive which Astro renderers the ejected
405
- // config wires in; React also switches on for project `.tsx`/`.jsx` and Ask AI.
490
+ // config wires in; React also switches on for project `.tsx`/`.jsx` and the assistant.
406
491
  const frameworks = new Set<string>([
407
492
  ...islands.islands.map((island) => island.framework),
408
493
  ...examples.examples.map((example) => example.framework),
409
494
  ...slotPlan.frameworks,
410
495
  ]);
411
- const needsReact = needsReactRaw || askEnabled || frameworks.has("react");
496
+ const needsReact =
497
+ needsReactRaw || assistantEnabled || frameworks.has("react");
412
498
  const needsVue = frameworks.has("vue");
413
499
  const needsSvelte = frameworks.has("svelte");
414
500
 
@@ -438,7 +524,9 @@ export const eject = async (
438
524
  pattern: page.pattern,
439
525
  })),
440
526
  ...mcpDiscoveryPages(project, pages),
527
+ ...playgroundProxyPages(config),
441
528
  ];
529
+ const openApiData = ejectOpenApiData(project);
442
530
 
443
531
  // Non-filesystem sources eject their materialized MDX into `<root>/blume-staged`
444
532
  // (a dedicated dir so it never clashes with a content root literally named
@@ -550,11 +638,11 @@ export const eject = async (
550
638
  // The header's Ask trigger behind the `blume:ask` alias. Always written — it
551
639
  // renders nothing when Ask is off — so the alias always resolves.
552
640
  {
553
- content: askComponentTemplate(askEnabled),
641
+ content: askComponentTemplate(assistantEnabled),
554
642
  path: join(genDir, "Ask.astro"),
555
643
  },
556
644
  {
557
- content: `${JSON.stringify(ejectOpenApiData(project))}\n`,
645
+ content: `${JSON.stringify(openApiData)}\n`,
558
646
  path: join(genDir, "openapi.json"),
559
647
  },
560
648
  {
@@ -580,30 +668,38 @@ export const eject = async (
580
668
  },
581
669
  ];
582
670
 
583
- if (askEnabled) {
671
+ files.push(...(await contentAssetFiles(project, root, srcDir, genDir)));
672
+
673
+ if (assistantEnabled) {
584
674
  files.push(...(await askFiles(project, srcDir, genDir)));
585
675
  }
586
676
 
677
+ // The `/changelog` index, mirrored from the generated runtime (`[]` when
678
+ // the project has no changelog). The OG endpoint below adds its card.
679
+ const changelog = changelogFiles(project, pages, srcDir, {
680
+ exportEpub,
681
+ exportPdf,
682
+ needsReact,
683
+ staged: hasStaged,
684
+ });
685
+
587
686
  if (config.seo.og.enabled) {
588
687
  files.push({
589
688
  content: ogEndpointTemplate(
590
689
  customOgRoutes(pages, config.title, config.seo.og.titles),
591
- { pageDescriptions: config.seo.og.description !== false }
690
+ { pageDescriptions: config.seo.og.description !== false },
691
+ changelog.length > 0
592
692
  ),
593
693
  path: join(srcDir, "pages", "og", "[...slug].png.ts"),
594
694
  });
595
695
  }
596
696
 
597
- // The hosted MCP server and the `/changelog` index, mirrored from the
598
- // generated runtime (each helper returns `[]` when its feature is off).
697
+ // The hosted MCP server and the playground's built-in proxy, mirrored from
698
+ // the generated runtime (`[]` when each is off).
599
699
  files.push(
600
700
  ...(await mcpFiles(project, pages, srcDir, genDir)),
601
- ...changelogFiles(project, pages, srcDir, {
602
- exportEpub,
603
- exportPdf,
604
- needsReact,
605
- staged: hasStaged,
606
- })
701
+ ...changelog,
702
+ ...playgroundProxyFiles(config, openApiData, srcDir)
607
703
  );
608
704
 
609
705
  // Default 404 page, unless the project already owns `/404` (a custom
@@ -672,7 +768,9 @@ export const eject = async (
672
768
  // the ejected app keeps its reference routes — including the warnings (a
673
769
  // missing spec file, a route collision), which the caller surfaces exactly
674
770
  // like the generated-runtime path does.
675
- const warnings: string[] = [];
771
+ // A proxied spec with no origin to allow gets the generated runtime's
772
+ // diagnostic too.
773
+ const warnings: string[] = proxyAllowlistWarnings(config, openApiData);
676
774
  if (hasScalarReferences(config)) {
677
775
  const references = await buildReferenceFiles({
678
776
  config,
@@ -16,6 +16,7 @@ import { applyAudienceVisibility } from "../ai/visibility.ts";
16
16
  import type { VisibilityAudience } from "../ai/visibility.ts";
17
17
  import matter from "../core/frontmatter.ts";
18
18
  import { parseHeadingMarkers } from "../core/heading-markers.ts";
19
+ import { EN_UI, resolveUIStrings } from "../core/i18n-ui.ts";
19
20
  import { contentIndexable } from "../core/manifest.ts";
20
21
  import type { BlumeProject } from "../core/project-graph.ts";
21
22
  import { HTML_COMMENT } from "../core/sources/normalize.ts";
@@ -149,7 +150,7 @@ const parseMarkdown = (
149
150
  format === "mdx" ? parseMdx(markdown) : markdownToMdast(markdown, MD_PARSE);
150
151
 
151
152
  // Fenced code is excluded from the plain index by default (ranking noise) —
152
- // the "markdown" extraction keeps it for Ask AI grounding. Code-heavy docs opt
153
+ // the "markdown" extraction keeps it for assistant grounding. Code-heavy docs opt
153
154
  // in via `search.indexing.includeCodeBlocks`, which indexes the fence body and
154
155
  // its rendered title (```ts blume.config.ts) but never the fence markers,
155
156
  // language, or other meta keywords.
@@ -383,7 +384,7 @@ const pageBody = async (
383
384
  *
384
385
  * `content` selects the extraction: `"plain"` (default) strips Markdown to bare
385
386
  * searchable text; `"markdown"` keeps the body's Markdown — code blocks, lists,
386
- * headings — for Ask AI grounding, where fenced examples are often the answer
387
+ * headings — for assistant grounding, where fenced examples are often the answer
387
388
  * and stripping them makes the model unable to cite content the docs do contain.
388
389
  * The `"markdown"` body is agent-facing, so components are downleveled with the
389
390
  * same serializers the `.md` mirror, llms-full.txt and MCP `get_page` use: a
@@ -447,6 +448,26 @@ export const buildSearchDocuments = async (
447
448
  // otherwise.
448
449
  const components = projectComponentSerializers(project);
449
450
 
451
+ // A page in no sidebar group falls under the "Docs" section, in its own
452
+ // language: the label is a UI string, resolved once per locale.
453
+ const { i18n } = project.config;
454
+ const docsSections = new Map<string, string>();
455
+ const docsSection = (locale: string): string => {
456
+ let label = docsSections.get(locale);
457
+ if (label === undefined) {
458
+ label = (
459
+ i18n
460
+ ? resolveUIStrings(locale, {
461
+ defaultLocale: i18n.defaultLocale,
462
+ overrides: i18n.ui,
463
+ })
464
+ : EN_UI
465
+ ).search.docs;
466
+ docsSections.set(locale, label);
467
+ }
468
+ return label;
469
+ };
470
+
450
471
  return await Promise.all(
451
472
  indexable.map(async (route) => {
452
473
  const page = pageById.get(route.id);
@@ -461,7 +482,7 @@ export const buildSearchDocuments = async (
461
482
  description: page?.description ?? "",
462
483
  locale: route.locale,
463
484
  route: route.path,
464
- section: crumb?.section || "Docs",
485
+ section: crumb?.section || docsSection(route.locale),
465
486
  tags: tags && tags.length > 0 ? tags : undefined,
466
487
  title: route.title,
467
488
  version: route.version,
@@ -298,7 +298,7 @@ const segmentingTokenizer = (locale?: string): Tokenizer | undefined => {
298
298
 
299
299
  /**
300
300
  * Build an in-memory Orama full-text index from search documents. Shared by the
301
- * Orama client loader (browser), the MCP server, and Ask AI grounding (Node),
301
+ * Orama client loader (browser), the MCP server, and assistant grounding (Node),
302
302
  * so ranking is identical wherever docs are queried. `locale` — the site's
303
303
  * `i18n.defaultLocale` — swaps in a word-segmenting tokenizer for every
304
304
  * non-Latin script, all of which Orama's default tokenizer reduces to zero
@@ -7,6 +7,19 @@ import type { SearchRecord } from "../documents.ts";
7
7
  /** The adapter options the sync reads (the public `apiKey` is never used). */
8
8
  export type AlgoliaSyncConfig = Pick<AlgoliaOptions, "appId" | "indexName">;
9
9
 
10
+ /**
11
+ * The record attributes the search dialog filters on (`facetFilters` on
12
+ * `locale:<code>` and `version:<id>`). In Algolia a facet filter on an
13
+ * attribute the index doesn't declare for faceting matches nothing — an i18n
14
+ * or versioned site would get no hits — so the sync declares them,
15
+ * filter-only.
16
+ */
17
+ const FILTER_ATTRIBUTES = ["locale", "version"] as const;
18
+
19
+ /** A faceting declaration's attribute: `filterOnly(locale)` → `locale`. */
20
+ const facetAttribute = (declaration: string): string =>
21
+ declaration.replace(/^\w+\((?<name>.*)\)$/u, "$<name>");
22
+
10
23
  /**
11
24
  * Upload the search records to Algolia. Uses the admin key from
12
25
  * `ALGOLIA_ADMIN_API_KEY` (never the adapter options, which hold only the
@@ -14,7 +27,9 @@ export type AlgoliaSyncConfig = Pick<AlgoliaOptions, "appId" | "indexName">;
14
27
  *
15
28
  * Uses `replaceAllObjects`, which atomically replaces the index contents, so
16
29
  * pages deleted or renamed since the last sync don't linger as stale search
17
- * hits that 404 when clicked.
30
+ * hits that 404 when clicked. Then adds `filterOnly(locale)` and
31
+ * `filterOnly(version)` to the index's `attributesForFaceting`, keeping any
32
+ * the site declared itself, so the dialog's locale and version filters match.
18
33
  */
19
34
  export const syncAlgolia = async (
20
35
  records: SearchRecord[],
@@ -28,8 +43,27 @@ export const syncAlgolia = async (
28
43
  // `core/node-require.ts`).
29
44
  const { algoliasearch }: typeof AlgoliaSdk = nodeRequire("algoliasearch");
30
45
  const client = algoliasearch(config.appId, adminKey);
46
+ const { indexName } = config;
31
47
  await client.replaceAllObjects({
32
- indexName: config.indexName,
48
+ indexName,
33
49
  objects: records.map((record) => ({ ...record, objectID: record._id })),
34
50
  });
51
+ const { attributesForFaceting = [] } = await client.getSettings({
52
+ indexName,
53
+ });
54
+ const declared = new Set(attributesForFaceting.map(facetAttribute));
55
+ const missing = FILTER_ATTRIBUTES.filter((name) => !declared.has(name));
56
+ if (missing.length === 0) {
57
+ return;
58
+ }
59
+ const { taskID } = await client.setSettings({
60
+ indexName,
61
+ indexSettings: {
62
+ attributesForFaceting: [
63
+ ...attributesForFaceting,
64
+ ...missing.map((name) => `filterOnly(${name})`),
65
+ ],
66
+ },
67
+ });
68
+ await client.waitForTask({ indexName, taskID });
35
69
  };
@@ -165,6 +165,11 @@ const sourceEntrySchema = z
165
165
  .custom<SourceAdapterInput>(
166
166
  (value) => isObjectLike(value) && "kind" in value,
167
167
  {
168
+ // `z.custom` marks its failure as aborting by default, which makes Zod
169
+ // skip even the `content` refinement that opts in to running after a
170
+ // failed field (the shorthand beside `sources`). The pipe below stops
171
+ // at this entry either way.
172
+ abort: false,
168
173
  error: (issue) => {
169
174
  const legacy = legacyEntrySchema.safeParse(issue.input);
170
175
  return entryHint(legacy.success ? legacy.data.type : undefined);
@@ -268,7 +268,7 @@ ${THEME_MAPPING}
268
268
  @apply block rounded-[0.65rem] px-2.5 py-1.5 text-muted-foreground text-sm transition-colors hover:bg-muted hover:text-foreground aria-[current=page]:bg-muted aria-[current=page]:font-medium aria-[current=page]:text-foreground;
269
269
  }
270
270
  @utility blume-nav-drill {
271
- @apply flex w-full items-center gap-2 rounded-[0.65rem] px-2.5 py-1.5 text-left font-medium text-foreground text-sm transition-colors hover:bg-muted;
271
+ @apply flex w-full items-center gap-2 rounded-[0.65rem] px-2.5 py-1.5 text-start font-medium text-foreground text-sm transition-colors hover:bg-muted;
272
272
  }
273
273
  @utility blume-nav-summary {
274
274
  @apply flex cursor-pointer list-none items-center gap-1.5 rounded-[0.65rem] px-2.5 py-1.5 text-muted-foreground text-sm transition-colors hover:bg-muted hover:text-foreground [&::-webkit-details-marker]:hidden;
@@ -884,11 +884,18 @@ pre:has(.line.focused):hover .line:not(.focused) {
884
884
  }
885
885
 
886
886
  /* Print / "Save as PDF" (the page-actions Export → PDF runs window.print()).
887
- Strip the surrounding chrome so the printout is just the article. */
887
+ Strip the surrounding chrome so the printout is just the article. Scoped to
888
+ the chrome itself, never bare \`header\`/\`aside\`: content renders those too
889
+ (every Callout and Panel is an <aside>, every changelog Update has a
890
+ <header>), and they belong in the printout. \`body > header\` keeps a
891
+ \`layout.Header\` override out as well. */
888
892
  @media print {
889
893
  [data-blume-banner],
890
- header,
891
- aside,
894
+ [data-blume-header],
895
+ body > header,
896
+ [data-blume-nav-drawer],
897
+ [data-blume-toc],
898
+ [data-blume-assistant-panel],
892
899
  [data-blume-page-actions],
893
900
  #blume-content > nav,
894
901
  #blume-content > details {
@@ -1,5 +1,5 @@
1
1
  import type { AgentKind } from "../audit/agent.ts";
2
- import { DISALLOWED_TOOLS } from "../eval/agents.ts";
2
+ import { CODEX_LOCKDOWN, DISALLOWED_TOOLS } from "../eval/agents.ts";
3
3
 
4
4
  /**
5
5
  * argv builders for the translator role. The subprocess machinery
@@ -28,6 +28,10 @@ const claudeArgs = (): string[] => [
28
28
  "1",
29
29
  ];
30
30
 
31
+ // `--sandbox read-only` alone still lets codex's shell read any file the user
32
+ // can, and the source page being translated is untrusted input (a remote
33
+ // contributor's prose), so the translator gets the same lockdown as an eval
34
+ // run: no shell, exec, or local-image tool, and no inherited environment.
31
35
  const codexArgs = (lastMessagePath: string): string[] => [
32
36
  "exec",
33
37
  "--skip-git-repo-check",
@@ -35,6 +39,7 @@ const codexArgs = (lastMessagePath: string): string[] => [
35
39
  "--ephemeral",
36
40
  "--sandbox",
37
41
  "read-only",
42
+ ...CODEX_LOCKDOWN,
38
43
  "--output-last-message",
39
44
  lastMessagePath,
40
45
  "-",
@@ -4,6 +4,7 @@ import { readFile } from "node:fs/promises";
4
4
  import { join } from "pathe";
5
5
  import { z } from "zod";
6
6
 
7
+ import { BlumeError } from "../core/diagnostics.ts";
7
8
  import { writeTextAtomic } from "../core/fs-atomic.ts";
8
9
 
9
10
  /**
@@ -39,21 +40,34 @@ export const emptyLedger = (): TranslationLedger => ({
39
40
  * every locale's stamp. sha256-16 like the audit snapshot's content hash.
40
41
  * Kept as its own function rather than sharing core's `hashText`: ledger
41
42
  * stamps persist in user repos, so this hash must never change shape when an
42
- * ephemeral cache hash does.
43
+ * ephemeral cache hash does. CRLF line endings are normalized to LF first, so
44
+ * a Windows checkout (`core.autocrlf`) hashes the same as everyone else's —
45
+ * LF text hashes exactly as it always has.
43
46
  */
44
47
  export const hashSource = (text: string): string =>
45
- createHash("sha256").update(text).digest("hex").slice(0, 16);
48
+ createHash("sha256")
49
+ .update(text.replaceAll("\r\n", "\n"))
50
+ .digest("hex")
51
+ .slice(0, 16);
52
+
53
+ /** A git merge-conflict marker line: `<<<<<<< ours`, `=======`, `>>>>>>> theirs`. */
54
+ const CONFLICT_MARKER = /^(?:<{7}|={7}|>{7})(?:[ \r]|$)/mu;
46
55
 
47
56
  /**
48
57
  * Read the ledger at `root`, tolerantly: a missing file, unparseable JSON, or
49
58
  * an unknown shape/version all resolve to an empty ledger rather than an error
50
59
  * (same posture as the dev lock's `parseLock`) — the worst outcome of a
51
60
  * corrupt ledger is retranslating files that were already up to date.
61
+ *
62
+ * Unresolved merge-conflict markers are the exception, and an error: read as
63
+ * empty, every existing translation would be adopted as current, so
64
+ * `--check` would pass over translations that are out of date.
52
65
  */
53
66
  export const readLedger = async (root: string): Promise<TranslationLedger> => {
67
+ const path = join(root, LEDGER_FILE);
54
68
  let raw: string;
55
69
  try {
56
- raw = await readFile(join(root, LEDGER_FILE), "utf-8");
70
+ raw = await readFile(path, "utf-8");
57
71
  } catch {
58
72
  return emptyLedger();
59
73
  }
@@ -61,6 +75,15 @@ export const readLedger = async (root: string): Promise<TranslationLedger> => {
61
75
  try {
62
76
  data = JSON.parse(raw);
63
77
  } catch {
78
+ if (CONFLICT_MARKER.test(raw)) {
79
+ throw new BlumeError({
80
+ code: "BLUME_TRANSLATE_LEDGER_CONFLICT",
81
+ file: path,
82
+ message: `${LEDGER_FILE} has unresolved merge-conflict markers, so it can't say which translations are current.`,
83
+ severity: "error",
84
+ suggestion: `Resolve the merge conflict in ${LEDGER_FILE}, then rerun.`,
85
+ });
86
+ }
64
87
  return emptyLedger();
65
88
  }
66
89
  const parsed = ledgerSchema.safeParse(data);
@@ -57,7 +57,9 @@ export const metaTargetPath = (
57
57
  /**
58
58
  * Discover the default-locale `meta.{ts,js,mjs}` files whose titles a
59
59
  * translation run covers. Skips (in order): non-`dir` i18n projects entirely,
60
- * files inside a configured locale directory (those ARE translations),
60
+ * files inside a configured locale directory (those ARE translations), files
61
+ * inside an archived-version snapshot (frozen, with their own translations —
62
+ * the same folders the navigation's meta lookup hoists as versions),
61
63
  * factory-form modules (a warning — the generator can't re-emit a function),
62
64
  * modules that fail meta validation (the scan already errors on those), and
63
65
  * modules with no `title` (nothing to translate).
@@ -74,6 +76,9 @@ export const discoverTranslatableMeta = async (
74
76
  locale.code === i18n.defaultLocale ? [] : [locale.code.toLowerCase()]
75
77
  )
76
78
  );
79
+ const versionDirs = new Set(
80
+ project.config.versions?.archived.map((version) => version.id)
81
+ );
77
82
 
78
83
  const load = createModuleLoader();
79
84
  const metas: TranslatableMeta[] = [];
@@ -92,8 +97,11 @@ export const discoverTranslatableMeta = async (
92
97
  });
93
98
  for (const file of files.toSorted()) {
94
99
  const dir = relative(contentRoot, dirname(file));
95
- const first = dir.split("/")[0]?.toLowerCase();
96
- if (first && localeDirs.has(first)) {
100
+ const [head] = dir.split("/");
101
+ if (
102
+ head &&
103
+ (localeDirs.has(head.toLowerCase()) || versionDirs.has(head))
104
+ ) {
97
105
  continue;
98
106
  }
99
107
  // oxlint-disable-next-line no-await-in-loop -- sequential, ordered discovery
@@ -185,7 +185,7 @@ const driftDiagnostics = (workList: TranslateWorkList): Diagnostic[] =>
185
185
  : `The "${row.locale}" translation of ${row.sourceRel} is stale: its source changed since it was translated.`,
186
186
  severity: "error",
187
187
  suggestion:
188
- "Run `blume translate --claude` (or `--codex`) to bring the translations up to date.",
188
+ "Run `blume translate --codex` (or `--claude`) to bring the translations up to date.",
189
189
  }));
190
190
 
191
191
  /** One line per missing/stale pair, plus dim lines for untracked adoptions. */