blume 1.7.3 → 2.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (690) hide show
  1. package/AGENTS.md +19 -0
  2. package/CHANGELOG.md +245 -0
  3. package/README.md +34 -21
  4. package/dist/cli/{chunk-vacwm2hv.js → chunk-27g6wdth.js} +2 -2
  5. package/dist/cli/{chunk-ps4m1xh4.js → chunk-2hn4b8z7.js} +513 -550
  6. package/dist/cli/chunk-2hn4b8z7.js.map +12 -0
  7. package/dist/cli/chunk-5shv93fd.js +39 -0
  8. package/dist/cli/chunk-5shv93fd.js.map +10 -0
  9. package/dist/cli/chunk-6crbhc3x.js +361 -0
  10. package/dist/cli/chunk-6crbhc3x.js.map +14 -0
  11. package/dist/cli/{chunk-8cjtbafj.js → chunk-6hsn950k.js} +109 -383
  12. package/dist/cli/chunk-6hsn950k.js.map +10 -0
  13. package/dist/cli/chunk-6vm74dry.js +148 -0
  14. package/dist/cli/chunk-6vm74dry.js.map +10 -0
  15. package/dist/cli/{chunk-yg63d42r.js → chunk-79jhk4py.js} +87 -58
  16. package/dist/cli/chunk-79jhk4py.js.map +35 -0
  17. package/dist/cli/chunk-82bbrxdn.js +51 -0
  18. package/dist/cli/chunk-82bbrxdn.js.map +10 -0
  19. package/dist/cli/chunk-abh8yjkn.js +31 -0
  20. package/dist/cli/chunk-abh8yjkn.js.map +10 -0
  21. package/dist/cli/chunk-ah61y8py.js +75 -0
  22. package/dist/cli/chunk-ah61y8py.js.map +11 -0
  23. package/dist/cli/{chunk-dwgcp5sm.js → chunk-ce574jw2.js} +1 -1
  24. package/dist/cli/chunk-ch6g3ar0.js +102 -0
  25. package/dist/cli/chunk-ch6g3ar0.js.map +10 -0
  26. package/dist/cli/chunk-dh8cwk36.js +279 -0
  27. package/dist/cli/chunk-dh8cwk36.js.map +10 -0
  28. package/dist/cli/{chunk-rqy0s5wh.js → chunk-epjnccmv.js} +17 -16
  29. package/dist/cli/{chunk-rqy0s5wh.js.map → chunk-epjnccmv.js.map} +3 -3
  30. package/dist/cli/chunk-f2z5v128.js +97 -0
  31. package/dist/cli/chunk-f2z5v128.js.map +10 -0
  32. package/dist/cli/{chunk-2z47ypj8.js → chunk-fa25z98p.js} +16 -3
  33. package/dist/cli/chunk-fa25z98p.js.map +11 -0
  34. package/dist/cli/{chunk-1jefwnfs.js → chunk-fs23ddbb.js} +1076 -2592
  35. package/dist/cli/chunk-fs23ddbb.js.map +35 -0
  36. package/dist/cli/{chunk-e7f42gdj.js → chunk-fxypxtvm.js} +2 -2
  37. package/dist/cli/{chunk-bvwwhd84.js → chunk-fz5wtpmh.js} +15 -15
  38. package/dist/cli/{chunk-bvwwhd84.js.map → chunk-fz5wtpmh.js.map} +1 -1
  39. package/dist/cli/chunk-hdpx1tax.js +91 -0
  40. package/dist/cli/chunk-hdpx1tax.js.map +10 -0
  41. package/dist/cli/{chunk-ct47dqpx.js → chunk-jts8mvcz.js} +67 -7
  42. package/dist/cli/{chunk-2mzebbbz.js.map → chunk-jts8mvcz.js.map} +6 -4
  43. package/dist/cli/{chunk-ahnw3kxw.js → chunk-jwyddg7y.js} +27 -22
  44. package/dist/cli/chunk-jwyddg7y.js.map +15 -0
  45. package/dist/cli/{chunk-cjtn640a.js → chunk-kdp5q7ke.js} +43 -20
  46. package/dist/cli/chunk-kdp5q7ke.js.map +10 -0
  47. package/dist/cli/{chunk-5g0w1e2c.js → chunk-kpf8rrjc.js} +28 -16
  48. package/dist/cli/{chunk-5g0w1e2c.js.map → chunk-kpf8rrjc.js.map} +4 -4
  49. package/dist/cli/chunk-m3vmjgmq.js +133 -0
  50. package/dist/cli/chunk-m3vmjgmq.js.map +10 -0
  51. package/dist/cli/{chunk-b27xqwn9.js → chunk-mb2919y2.js} +9 -5
  52. package/dist/cli/chunk-mb2919y2.js.map +10 -0
  53. package/dist/cli/{chunk-5qk08vmp.js → chunk-q5163e60.js} +133 -54
  54. package/dist/cli/chunk-q5163e60.js.map +11 -0
  55. package/dist/cli/chunk-qkqwkpte.js +12437 -0
  56. package/dist/cli/chunk-qkqwkpte.js.map +182 -0
  57. package/dist/cli/{chunk-4x36ddpw.js → chunk-qs4q5p4e.js} +81 -87
  58. package/dist/cli/chunk-qs4q5p4e.js.map +10 -0
  59. package/dist/cli/{chunk-0xjyb285.js → chunk-qwsrynx5.js} +15 -5
  60. package/dist/cli/{chunk-0xjyb285.js.map → chunk-qwsrynx5.js.map} +4 -4
  61. package/dist/cli/chunk-s1p84fyh.js +261 -0
  62. package/dist/cli/chunk-s1p84fyh.js.map +10 -0
  63. package/dist/cli/chunk-s6jhgk0q.js +176 -0
  64. package/dist/cli/chunk-s6jhgk0q.js.map +11 -0
  65. package/dist/cli/chunk-vtk4a6dg.js +374 -0
  66. package/dist/cli/chunk-vtk4a6dg.js.map +10 -0
  67. package/dist/cli/{chunk-k79xp7av.js → chunk-wgm7m9qk.js} +230 -700
  68. package/dist/cli/chunk-wgm7m9qk.js.map +36 -0
  69. package/dist/cli/chunk-wm7js3j9.js +145 -0
  70. package/dist/cli/chunk-wm7js3j9.js.map +11 -0
  71. package/dist/cli/chunk-yt5n7ppj.js +79 -0
  72. package/dist/cli/chunk-yt5n7ppj.js.map +10 -0
  73. package/dist/cli/{chunk-3r45185y.js → chunk-yw7dm696.js} +9 -11
  74. package/dist/cli/{chunk-3r45185y.js.map → chunk-yw7dm696.js.map} +3 -3
  75. package/dist/cli/{chunk-nn13znc2.js → chunk-zxcczpyx.js} +1 -1
  76. package/dist/cli/chunk-zxh4d9vy.js +122 -0
  77. package/dist/cli/chunk-zxh4d9vy.js.map +11 -0
  78. package/dist/cli/index.js +214 -57
  79. package/dist/cli/index.js.map +8 -7
  80. package/dist/types/ai/agent-readability.d.ts +52 -0
  81. package/dist/types/ai/ai-catalog.d.ts +42 -0
  82. package/dist/types/ai/api/paths.d.ts +17 -0
  83. package/dist/types/ai/api-catalog.d.ts +18 -0
  84. package/dist/types/ai/ask-context.d.ts +7 -7
  85. package/dist/types/ai/ask.d.ts +368 -0
  86. package/dist/types/ai/changelog-markdown.d.ts +2 -0
  87. package/dist/types/ai/component-markdown.d.ts +2 -2
  88. package/dist/types/ai/index.d.ts +24 -0
  89. package/dist/types/ai/link-headers.d.ts +24 -0
  90. package/dist/types/ai/llms.d.ts +25 -0
  91. package/dist/types/ai/markdown.d.ts +45 -0
  92. package/dist/types/ai/mcp/discovery.d.ts +68 -0
  93. package/dist/types/ai/mcp/tools.d.ts +16 -0
  94. package/dist/types/ai/openapi-components.d.ts +43 -0
  95. package/dist/types/ai/relative-links.d.ts +26 -0
  96. package/dist/types/ai/serializers.d.ts +15 -0
  97. package/dist/types/ai/skills.d.ts +42 -0
  98. package/dist/types/ai/tar.d.ts +25 -0
  99. package/dist/types/ai/visibility.d.ts +17 -0
  100. package/dist/types/ai/web-bot-auth.d.ts +16 -0
  101. package/dist/types/analytics/adobe.d.ts +35 -0
  102. package/dist/types/analytics/amplitude.d.ts +50 -0
  103. package/dist/types/analytics/clarity.d.ts +31 -0
  104. package/dist/types/analytics/clearbit.d.ts +29 -0
  105. package/dist/types/analytics/cloudflare.d.ts +41 -0
  106. package/dist/types/analytics/fathom.d.ts +41 -0
  107. package/dist/types/analytics/google-analytics.d.ts +44 -0
  108. package/dist/types/analytics/google-tag-manager.d.ts +40 -0
  109. package/dist/types/analytics/head.d.ts +30 -0
  110. package/dist/types/analytics/heap.d.ts +40 -0
  111. package/dist/types/analytics/hightouch.d.ts +44 -0
  112. package/dist/types/analytics/hotjar.d.ts +33 -0
  113. package/dist/types/analytics/index.d.ts +61 -0
  114. package/dist/types/analytics/inline.d.ts +17 -0
  115. package/dist/types/analytics/logrocket.d.ts +44 -0
  116. package/dist/types/analytics/mixpanel.d.ts +67 -0
  117. package/dist/types/analytics/pirsch.d.ts +42 -0
  118. package/dist/types/analytics/plausible.d.ts +56 -0
  119. package/dist/types/analytics/posthog.d.ts +45 -0
  120. package/dist/types/analytics/schema.d.ts +320 -0
  121. package/dist/types/analytics/script.d.ts +46 -0
  122. package/dist/types/analytics/segment.d.ts +50 -0
  123. package/dist/types/analytics/vercel.d.ts +48 -0
  124. package/dist/types/astro/integration.d.ts +76 -0
  125. package/dist/types/astro/markdown-negotiation.d.ts +23 -0
  126. package/dist/types/astro/module-types.d.ts +14 -0
  127. package/dist/types/astro/pages.d.ts +44 -0
  128. package/dist/types/cli/env.d.ts +12 -0
  129. package/dist/types/cli/init/scaffold.d.ts +154 -0
  130. package/dist/types/components/layout/nav-utils.d.ts +11 -0
  131. package/dist/types/core/adapter.d.ts +47 -0
  132. package/dist/types/core/api-name.d.ts +7 -0
  133. package/dist/types/core/changelog-index.d.ts +13 -0
  134. package/dist/types/core/config-input.d.ts +197 -526
  135. package/dist/types/core/config.d.ts +61 -35
  136. package/dist/types/core/content-assets.d.ts +76 -0
  137. package/dist/types/core/custom-pages.d.ts +33 -0
  138. package/dist/types/core/data.d.ts +30 -15
  139. package/dist/types/core/define-components.d.ts +12 -9
  140. package/dist/types/core/deployment-env.d.ts +6 -11
  141. package/dist/types/core/frontmatter.d.ts +10 -0
  142. package/dist/types/core/graph.d.ts +18 -0
  143. package/dist/types/core/heading-markers.d.ts +54 -0
  144. package/dist/types/core/i18n-ui.d.ts +12 -10
  145. package/dist/types/core/i18n.d.ts +87 -0
  146. package/dist/types/core/includes.d.ts +138 -0
  147. package/dist/types/core/last-modified.d.ts +47 -0
  148. package/dist/types/core/links.d.ts +95 -0
  149. package/dist/types/core/locale-links.d.ts +60 -0
  150. package/dist/types/core/manifest.d.ts +17 -0
  151. package/dist/types/core/meta.d.ts +38 -0
  152. package/dist/types/core/nav-diagnostics.d.ts +26 -0
  153. package/dist/types/core/navigation.d.ts +6 -0
  154. package/dist/types/core/node-require.d.ts +19 -0
  155. package/dist/types/core/package-json.d.ts +13 -0
  156. package/dist/types/core/probe.d.ts +42 -0
  157. package/dist/types/core/project-graph.d.ts +51 -0
  158. package/dist/types/core/project.d.ts +2 -0
  159. package/dist/types/core/safe-href.d.ts +2 -0
  160. package/dist/types/core/safe-links.d.ts +26 -0
  161. package/dist/types/core/schema.d.ts +2284 -454
  162. package/dist/types/core/site-url.d.ts +16 -0
  163. package/dist/types/core/sources/assets.d.ts +36 -0
  164. package/dist/types/core/sources/cache.d.ts +37 -0
  165. package/dist/types/core/sources/collection.d.ts +28 -0
  166. package/dist/types/core/sources/contentful-rich-text.d.ts +31 -0
  167. package/dist/types/core/sources/contentful.d.ts +40 -0
  168. package/dist/types/core/sources/filesystem.d.ts +24 -0
  169. package/dist/types/core/sources/github-releases.d.ts +31 -0
  170. package/dist/types/core/sources/json.d.ts +30 -0
  171. package/dist/types/core/sources/lexical.d.ts +19 -0
  172. package/dist/types/core/sources/lower.d.ts +75 -0
  173. package/dist/types/core/sources/mdx-remote.d.ts +28 -0
  174. package/dist/types/core/sources/normalize.d.ts +147 -0
  175. package/dist/types/core/sources/notion.d.ts +131 -0
  176. package/dist/types/core/sources/obsidian.d.ts +46 -0
  177. package/dist/types/core/sources/payload.d.ts +39 -0
  178. package/dist/types/core/sources/portable-text.d.ts +42 -0
  179. package/dist/types/core/sources/read.d.ts +23 -0
  180. package/dist/types/core/sources/remote.d.ts +74 -0
  181. package/dist/types/core/sources/resolve.d.ts +19 -0
  182. package/dist/types/core/sources/sanity.d.ts +43 -0
  183. package/dist/types/core/sources/strapi-blocks.d.ts +12 -0
  184. package/dist/types/core/sources/strapi.d.ts +33 -0
  185. package/dist/types/core/sources/types.d.ts +7 -0
  186. package/dist/types/core/sources/watch.d.ts +45 -0
  187. package/dist/types/core/text-width.d.ts +11 -0
  188. package/dist/types/core/types.d.ts +15 -1
  189. package/dist/types/core/unrecognized-keys.d.ts +7 -0
  190. package/dist/types/core/versions.d.ts +72 -0
  191. package/dist/types/core/yaml.d.ts +9 -0
  192. package/dist/types/deploy/adapter-output.d.ts +45 -0
  193. package/dist/types/deploy/adapters/cloudflare.d.ts +40 -0
  194. package/dist/types/deploy/adapters/index.d.ts +29 -0
  195. package/dist/types/deploy/adapters/netlify.d.ts +37 -0
  196. package/dist/types/deploy/adapters/node.d.ts +37 -0
  197. package/dist/types/deploy/adapters/registry.d.ts +133 -0
  198. package/dist/types/deploy/adapters/types.d.ts +71 -0
  199. package/dist/types/deploy/adapters/vercel.d.ts +38 -0
  200. package/dist/types/deploy/artifacts.d.ts +65 -0
  201. package/dist/types/deploy/cloudflare-negotiation.d.ts +196 -0
  202. package/dist/types/deploy/function-bundle.d.ts +80 -0
  203. package/dist/types/deploy/headers.d.ts +50 -0
  204. package/dist/types/deploy/node-headers.d.ts +42 -0
  205. package/dist/types/deploy/platforms/cloudflare.d.ts +40 -0
  206. package/dist/types/deploy/platforms/index.d.ts +18 -0
  207. package/dist/types/deploy/platforms/netlify.d.ts +13 -0
  208. package/dist/types/deploy/platforms/node.d.ts +11 -0
  209. package/dist/types/deploy/platforms/paths.d.ts +27 -0
  210. package/dist/types/deploy/platforms/static.d.ts +10 -0
  211. package/dist/types/deploy/platforms/types.d.ts +104 -0
  212. package/dist/types/deploy/platforms/vercel.d.ts +32 -0
  213. package/dist/types/deploy/redirects.d.ts +53 -0
  214. package/dist/types/deploy/robots.d.ts +8 -0
  215. package/dist/types/deploy/rss.d.ts +31 -0
  216. package/dist/types/deploy/sitemap.d.ts +21 -0
  217. package/dist/types/deploy/vercel-negotiation.d.ts +109 -0
  218. package/dist/types/markdown/code-title.d.ts +32 -0
  219. package/dist/types/markdown/fence-meta.d.ts +23 -0
  220. package/dist/types/markdown/themes.d.ts +3 -3
  221. package/dist/types/openapi/asyncapi.d.ts +129 -0
  222. package/dist/types/openapi/graphql-build.d.ts +8 -0
  223. package/dist/types/openapi/graphql.d.ts +122 -0
  224. package/dist/types/openapi/model.d.ts +158 -0
  225. package/dist/types/openapi/parse.d.ts +57 -0
  226. package/dist/types/openapi/references.d.ts +37 -25
  227. package/dist/types/openapi/render-mdx.d.ts +33 -0
  228. package/dist/types/openapi/sentence.d.ts +7 -0
  229. package/dist/types/openapi/signature.d.ts +10 -0
  230. package/dist/types/openapi/source.d.ts +22 -0
  231. package/dist/types/openapi/spec-dependency-error.d.ts +10 -0
  232. package/dist/types/reference/asyncapi.d.ts +166 -0
  233. package/dist/types/reference/graphql.d.ts +181 -0
  234. package/dist/types/reference/index.d.ts +32 -0
  235. package/dist/types/reference/openapi.d.ts +165 -0
  236. package/dist/types/reference/options.d.ts +157 -0
  237. package/dist/types/reference/scalar.d.ts +136 -0
  238. package/dist/types/reference/schema.d.ts +630 -0
  239. package/dist/types/search/adapters/algolia.d.ts +32 -0
  240. package/dist/types/search/adapters/flexsearch.d.ts +12 -0
  241. package/dist/types/search/adapters/index.d.ts +31 -0
  242. package/dist/types/search/adapters/mixedbread.d.ts +22 -0
  243. package/dist/types/search/adapters/orama-cloud.d.ts +33 -0
  244. package/dist/types/search/adapters/orama.d.ts +13 -0
  245. package/dist/types/search/adapters/pagefind.d.ts +12 -0
  246. package/dist/types/search/adapters/registry.d.ts +228 -0
  247. package/dist/types/search/adapters/types.d.ts +34 -0
  248. package/dist/types/search/adapters/typesense.d.ts +42 -0
  249. package/dist/types/search/build.d.ts +23 -0
  250. package/dist/types/search/documents.d.ts +90 -0
  251. package/dist/types/search/facets.d.ts +3 -0
  252. package/dist/types/search/orama-index.d.ts +1 -1
  253. package/dist/types/search/sync/algolia.d.ts +14 -0
  254. package/dist/types/search/sync/index.d.ts +14 -0
  255. package/dist/types/search/sync/orama-cloud.d.ts +10 -0
  256. package/dist/types/search/sync/typesense.d.ts +14 -0
  257. package/dist/types/sources/contentful.d.ts +68 -0
  258. package/dist/types/sources/custom.d.ts +20 -0
  259. package/dist/types/sources/filesystem.d.ts +42 -0
  260. package/dist/types/sources/github-releases.d.ts +46 -0
  261. package/dist/types/sources/index.d.ts +48 -0
  262. package/dist/types/sources/mdx-remote.d.ts +63 -0
  263. package/dist/types/sources/notion.d.ts +65 -0
  264. package/dist/types/sources/obsidian.d.ts +34 -0
  265. package/dist/types/sources/payload.d.ts +68 -0
  266. package/dist/types/sources/registry.d.ts +1414 -0
  267. package/dist/types/sources/sanity.d.ts +69 -0
  268. package/dist/types/sources/shared.d.ts +46 -0
  269. package/dist/types/sources/strapi.d.ts +66 -0
  270. package/dist/types/theme/icon-kind.d.ts +11 -0
  271. package/dist/types/theme/icons.d.ts +20 -0
  272. package/docs/01-quickstart.mdx +18 -18
  273. package/docs/02-deployment.mdx +52 -28
  274. package/docs/03-upgrading.mdx +364 -0
  275. package/docs/04-migrating.mdx +58 -0
  276. package/docs/08-faq.mdx +5 -5
  277. package/docs/advanced/blog.mdx +13 -6
  278. package/docs/advanced/changelog.mdx +23 -35
  279. package/docs/advanced/custom-pages.mdx +22 -10
  280. package/docs/advanced/meta.ts +1 -8
  281. package/docs/advanced/skills.mdx +8 -0
  282. package/docs/cli/audit.mdx +646 -0
  283. package/docs/cli/doctor.mdx +29 -0
  284. package/docs/{reference/eval.mdx → cli/evals.mdx} +12 -11
  285. package/docs/cli/index.mdx +105 -0
  286. package/docs/cli/meta.ts +7 -0
  287. package/docs/{reference → cli}/translate.mdx +9 -9
  288. package/docs/cli/validate.mdx +41 -0
  289. package/docs/cli/version.mdx +40 -0
  290. package/docs/configuration/analytics.mdx +350 -59
  291. package/docs/configuration/assistant.mdx +357 -0
  292. package/docs/configuration/customization.mdx +16 -10
  293. package/docs/configuration/index.mdx +50 -31
  294. package/docs/configuration/meta.ts +1 -1
  295. package/docs/configuration/search.mdx +76 -55
  296. package/docs/configuration/theming.mdx +24 -15
  297. package/docs/content/components.mdx +21 -5
  298. package/docs/{reference → content}/frontmatter.mdx +35 -1
  299. package/docs/content/i18n.mdx +9 -7
  300. package/docs/content/includes.mdx +2 -4
  301. package/docs/content/index.mdx +1 -1
  302. package/docs/content/islands.mdx +11 -6
  303. package/docs/content/meta.mdx +1 -1
  304. package/docs/content/meta.ts +1 -0
  305. package/docs/content/navigation.mdx +9 -7
  306. package/docs/content/sources.mdx +145 -50
  307. package/docs/content/syntax.mdx +16 -12
  308. package/docs/content/versioning.mdx +3 -3
  309. package/docs/discoverability/agent-discovery.mdx +12 -12
  310. package/docs/discoverability/index.mdx +4 -4
  311. package/docs/discoverability/json-api.mdx +4 -4
  312. package/docs/discoverability/llms-txt.mdx +6 -6
  313. package/docs/discoverability/markdown.mdx +4 -4
  314. package/docs/discoverability/mcp.mdx +11 -11
  315. package/docs/discoverability/sitemap-and-robots.mdx +3 -3
  316. package/docs/index.mdx +4 -4
  317. package/docs/references/asyncapi.mdx +59 -0
  318. package/docs/{advanced → references}/graphql.mdx +45 -32
  319. package/docs/{reference → references}/meta.ts +2 -2
  320. package/docs/references/openapi.mdx +171 -0
  321. package/docs/references/scalar.mdx +64 -0
  322. package/package.json +42 -8
  323. package/skills/blume/SKILL.md +23 -10
  324. package/skills/blume-migrate/SKILL.md +22 -21
  325. package/skills/blume-migrate/assets/oxfmt@0.67.0.patch +49 -0
  326. package/skills/blume-migrate/references/docusaurus.md +5 -4
  327. package/skills/blume-migrate/references/fumadocs.md +4 -4
  328. package/skills/blume-migrate/references/mintlify.md +23 -8
  329. package/skills/blume-migrate/references/monorepo.md +6 -6
  330. package/skills/blume-migrate/references/starlight.md +4 -4
  331. package/src/ai/agent-readability.ts +25 -26
  332. package/src/ai/ai-catalog.ts +50 -32
  333. package/src/ai/api/handlers.ts +46 -11
  334. package/src/ai/api/paths.ts +1 -1
  335. package/src/ai/api-catalog.ts +8 -8
  336. package/src/ai/ask-context.ts +7 -7
  337. package/src/ai/ask-data.ts +3 -3
  338. package/src/ai/ask.ts +637 -100
  339. package/src/ai/changelog-markdown.ts +91 -0
  340. package/src/ai/component-markdown.ts +328 -12
  341. package/src/ai/cors.ts +3 -3
  342. package/src/ai/index.ts +45 -0
  343. package/src/ai/link-headers.ts +7 -6
  344. package/src/ai/llms.ts +20 -15
  345. package/src/ai/markdown.ts +35 -5
  346. package/src/ai/mcp/data.ts +9 -5
  347. package/src/ai/openapi-components.ts +5 -2
  348. package/src/ai/relative-links.ts +170 -0
  349. package/src/ai/serializers.ts +3 -3
  350. package/src/ai/skills.ts +1 -1
  351. package/src/ai/visibility.ts +1 -1
  352. package/src/ai/web-bot-auth.ts +2 -2
  353. package/src/analytics/adobe.ts +46 -0
  354. package/src/analytics/amplitude.ts +79 -0
  355. package/src/analytics/clarity.ts +46 -0
  356. package/src/analytics/clearbit.ts +47 -0
  357. package/src/analytics/cloudflare.ts +67 -0
  358. package/src/analytics/fathom.ts +64 -0
  359. package/src/analytics/google-analytics.ts +84 -0
  360. package/src/analytics/google-tag-manager.ts +61 -0
  361. package/src/analytics/head.ts +130 -0
  362. package/src/analytics/heap.ts +65 -0
  363. package/src/analytics/hightouch.ts +77 -0
  364. package/src/analytics/hotjar.ts +47 -0
  365. package/src/analytics/index.ts +71 -0
  366. package/src/analytics/inline.ts +21 -0
  367. package/src/analytics/logrocket.ts +71 -0
  368. package/src/analytics/mixpanel.ts +92 -0
  369. package/src/analytics/pirsch.ts +66 -0
  370. package/src/analytics/plausible.ts +83 -0
  371. package/src/analytics/posthog.ts +78 -0
  372. package/src/analytics/schema.ts +60 -0
  373. package/src/analytics/script.ts +60 -0
  374. package/src/analytics/segment.ts +85 -0
  375. package/src/analytics/vercel.ts +50 -0
  376. package/src/astro/adapter-root.ts +7 -9
  377. package/src/astro/component-slots.ts +131 -91
  378. package/src/astro/generate.ts +129 -361
  379. package/src/astro/integration.ts +2 -6
  380. package/src/astro/module-types.ts +1 -1
  381. package/src/astro/pages.ts +13 -101
  382. package/src/astro/render-deps.ts +379 -0
  383. package/src/astro/runtime-deps.ts +201 -0
  384. package/src/astro/templates.ts +575 -562
  385. package/src/audit/agent.ts +24 -0
  386. package/src/audit/catalog.ts +2 -2
  387. package/src/audit/checks/assets.ts +2 -2
  388. package/src/audit/checks/dns-aid.ts +1 -1
  389. package/src/audit/checks/duplicates.ts +3 -1
  390. package/src/audit/checks/i18n.ts +1 -1
  391. package/src/audit/checks/indexability.ts +7 -7
  392. package/src/audit/checks/links.ts +2 -2
  393. package/src/audit/checks/llms.ts +6 -6
  394. package/src/audit/checks/network.ts +3 -3
  395. package/src/audit/checks/og-image.ts +2 -2
  396. package/src/audit/checks/robots.ts +1 -1
  397. package/src/audit/checks/sitemap.ts +2 -2
  398. package/src/audit/checks/social.ts +1 -1
  399. package/src/audit/run.ts +4 -3
  400. package/src/audit/terms.ts +31 -0
  401. package/src/audit/url.ts +13 -13
  402. package/src/blume-modules.d.ts +2 -2
  403. package/src/cli/command-meta.ts +10 -0
  404. package/src/cli/commands/audit.ts +40 -21
  405. package/src/cli/commands/build.ts +71 -316
  406. package/src/cli/commands/check.ts +1 -0
  407. package/src/cli/commands/dev.ts +40 -1
  408. package/src/cli/commands/doctor.ts +92 -14
  409. package/src/cli/commands/eject.ts +44 -7
  410. package/src/cli/commands/eval.ts +5 -5
  411. package/src/cli/commands/init.ts +161 -41
  412. package/src/cli/commands/migrate.ts +121 -0
  413. package/src/cli/commands/preview.ts +7 -1
  414. package/src/cli/commands/translate.ts +5 -5
  415. package/src/cli/commands/upgrade.ts +141 -0
  416. package/src/cli/commands/version.ts +57 -44
  417. package/src/cli/eject-scripts.ts +121 -6
  418. package/src/cli/index.ts +11 -1
  419. package/src/cli/init/install.ts +70 -0
  420. package/src/cli/init/questions.ts +7 -0
  421. package/src/cli/init/scaffold.ts +459 -75
  422. package/src/cli/lazy-command.ts +37 -1
  423. package/src/cli/prepare.ts +40 -6
  424. package/src/cli/required-secrets.ts +39 -12
  425. package/src/cli/unknown-flags.ts +266 -0
  426. package/src/cli/yarn-pnp.ts +52 -0
  427. package/src/components/content/AccordionItem.astro +7 -1
  428. package/src/components/content/Card.astro +4 -3
  429. package/src/components/content/ColorItem.astro +22 -4
  430. package/src/components/content/GithubInfo.astro +2 -2
  431. package/src/components/content/Prompt.astro +25 -25
  432. package/src/components/content/Tabs.astro +3 -1
  433. package/src/components/content/Tile.astro +2 -3
  434. package/src/components/content/Tooltip.astro +57 -9
  435. package/src/components/content/content-strings.ts +34 -0
  436. package/src/components/content/diff.ts +1 -1
  437. package/src/components/content/mermaid-element.ts +13 -2
  438. package/src/components/content/prompt-markdown.ts +292 -0
  439. package/src/components/content/tooltip-id.ts +41 -0
  440. package/src/components/copy-feedback.ts +1 -1
  441. package/src/components/islands/{AskAI.astro → Assistant.astro} +10 -10
  442. package/src/components/islands/{ask-ai.tsx → assistant.tsx} +23 -23
  443. package/src/components/islands/hooks.ts +36 -11
  444. package/src/components/layout/Analytics.astro +21 -79
  445. package/src/components/layout/Banner.astro +3 -1
  446. package/src/components/layout/DiscoveryLinks.astro +69 -0
  447. package/src/components/layout/Header.astro +93 -30
  448. package/src/components/layout/LanguageSwitcher.astro +4 -1
  449. package/src/components/layout/Logo.astro +23 -2
  450. package/src/components/layout/NavSelector.astro +13 -2
  451. package/src/components/layout/NavTree.astro +12 -3
  452. package/src/components/layout/NavTreeScript.astro +17 -3
  453. package/src/components/layout/PageActions.astro +3 -3
  454. package/src/components/layout/PageFeedback.astro +11 -1
  455. package/src/components/layout/PageLayout.astro +58 -11
  456. package/src/components/layout/Pagination.astro +7 -7
  457. package/src/components/layout/ReferenceLayout.astro +22 -13
  458. package/src/components/layout/RootLayout.astro +75 -72
  459. package/src/components/layout/Search.astro +43 -19
  460. package/src/components/layout/WebMcp.astro +1 -1
  461. package/src/components/layout/analytics-client.ts +73 -16
  462. package/src/components/layout/drawer-inert.ts +113 -15
  463. package/src/components/layout/dropdown-clamp.ts +105 -0
  464. package/src/components/layout/head-scripts.ts +15 -6
  465. package/src/components/layout/nav-utils.ts +17 -0
  466. package/src/components/layout/search/algolia.ts +8 -8
  467. package/src/components/layout/search/orama-cloud.ts +9 -7
  468. package/src/components/layout/search/typesense.ts +11 -17
  469. package/src/components/openapi/MessageComposer.astro +2 -2
  470. package/src/components/openapi/Playground.astro +2 -2
  471. package/src/components/openapi/description.ts +2 -2
  472. package/src/components/openapi/playground-client.ts +79 -10
  473. package/src/core/changelog-index.ts +24 -0
  474. package/src/core/code-fences.ts +1 -1
  475. package/src/core/component-overrides.ts +399 -154
  476. package/src/core/config-input.ts +201 -566
  477. package/src/core/config.ts +131 -42
  478. package/src/core/custom-pages.ts +105 -0
  479. package/src/core/data.ts +34 -15
  480. package/src/core/define-components.ts +12 -9
  481. package/src/core/deployment-env.ts +18 -74
  482. package/src/core/diagnostics.ts +14 -7
  483. package/src/core/graph.ts +69 -25
  484. package/src/core/i18n-ui.ts +41 -11
  485. package/src/core/i18n.ts +17 -0
  486. package/src/core/includes.ts +156 -38
  487. package/src/core/last-modified.ts +6 -11
  488. package/src/core/links.ts +166 -2
  489. package/src/core/manifest.ts +10 -3
  490. package/src/core/navigation.ts +115 -16
  491. package/src/core/new-tab.ts +35 -0
  492. package/src/core/node-require.ts +21 -0
  493. package/src/core/project-graph.ts +42 -26
  494. package/src/core/project.ts +9 -4
  495. package/src/core/request-body.ts +61 -0
  496. package/src/core/safe-href.ts +28 -0
  497. package/src/core/safe-links.ts +68 -0
  498. package/src/core/schema.ts +528 -745
  499. package/src/core/server-features.ts +10 -11
  500. package/src/core/sources/assets.ts +47 -11
  501. package/src/core/sources/collection.ts +67 -0
  502. package/src/core/sources/contentful-rich-text.ts +285 -0
  503. package/src/core/sources/contentful.ts +173 -0
  504. package/src/core/sources/github-releases.ts +51 -1
  505. package/src/core/sources/json.ts +71 -0
  506. package/src/core/sources/lexical.ts +195 -0
  507. package/src/core/sources/lower.ts +226 -0
  508. package/src/core/sources/normalize.ts +52 -2
  509. package/src/core/sources/notion.ts +39 -28
  510. package/src/core/sources/payload.ts +135 -0
  511. package/src/core/sources/portable-text.ts +11 -16
  512. package/src/core/sources/remote.ts +226 -0
  513. package/src/core/sources/resolve.ts +104 -166
  514. package/src/core/sources/sanity.ts +16 -49
  515. package/src/core/sources/strapi-blocks.ts +124 -0
  516. package/src/core/sources/strapi.ts +191 -0
  517. package/src/core/sources/types.ts +12 -1
  518. package/src/core/types.ts +15 -1
  519. package/src/core/ui-packs/ar.ts +10 -7
  520. package/src/core/ui-packs/bg.ts +10 -7
  521. package/src/core/ui-packs/bn.ts +10 -7
  522. package/src/core/ui-packs/ca.ts +7 -6
  523. package/src/core/ui-packs/cs.ts +10 -7
  524. package/src/core/ui-packs/da.ts +10 -7
  525. package/src/core/ui-packs/de.ts +10 -7
  526. package/src/core/ui-packs/el.ts +7 -6
  527. package/src/core/ui-packs/es.ts +7 -6
  528. package/src/core/ui-packs/fa.ts +10 -7
  529. package/src/core/ui-packs/fi.ts +10 -7
  530. package/src/core/ui-packs/fr.ts +7 -6
  531. package/src/core/ui-packs/he.ts +10 -7
  532. package/src/core/ui-packs/hi.ts +10 -7
  533. package/src/core/ui-packs/hr.ts +10 -7
  534. package/src/core/ui-packs/hu.ts +10 -7
  535. package/src/core/ui-packs/id.ts +10 -7
  536. package/src/core/ui-packs/it.ts +7 -6
  537. package/src/core/ui-packs/ja.ts +7 -6
  538. package/src/core/ui-packs/ko.ts +7 -6
  539. package/src/core/ui-packs/nl.ts +10 -7
  540. package/src/core/ui-packs/no.ts +10 -7
  541. package/src/core/ui-packs/pl.ts +10 -7
  542. package/src/core/ui-packs/pt-br.ts +7 -6
  543. package/src/core/ui-packs/pt.ts +7 -6
  544. package/src/core/ui-packs/ro.ts +10 -7
  545. package/src/core/ui-packs/ru.ts +10 -7
  546. package/src/core/ui-packs/sk.ts +10 -7
  547. package/src/core/ui-packs/sr.ts +10 -7
  548. package/src/core/ui-packs/sv.ts +10 -7
  549. package/src/core/ui-packs/th.ts +7 -6
  550. package/src/core/ui-packs/tr.ts +10 -7
  551. package/src/core/ui-packs/uk.ts +10 -7
  552. package/src/core/ui-packs/vi.ts +7 -6
  553. package/src/core/ui-packs/zh-tw.ts +7 -6
  554. package/src/core/ui-packs/zh.ts +7 -6
  555. package/src/core/unrecognized-keys.ts +10 -0
  556. package/src/core/version-cut.ts +116 -8
  557. package/src/deploy/adapter-output.ts +57 -97
  558. package/src/deploy/adapters/cloudflare.ts +39 -0
  559. package/src/deploy/adapters/index.ts +41 -0
  560. package/src/deploy/adapters/netlify.ts +34 -0
  561. package/src/deploy/adapters/node.ts +34 -0
  562. package/src/deploy/adapters/registry.ts +127 -0
  563. package/src/deploy/adapters/types.ts +92 -0
  564. package/src/deploy/adapters/vercel.ts +35 -0
  565. package/src/deploy/artifacts.ts +55 -27
  566. package/src/deploy/cloudflare-negotiation.ts +179 -100
  567. package/src/deploy/function-bundle.ts +18 -3
  568. package/src/deploy/headers.ts +124 -23
  569. package/src/deploy/node-headers.ts +198 -0
  570. package/src/deploy/platforms/cloudflare.ts +312 -0
  571. package/src/deploy/platforms/index.ts +67 -0
  572. package/src/deploy/platforms/netlify.ts +52 -0
  573. package/src/deploy/platforms/node.ts +40 -0
  574. package/src/deploy/platforms/paths.ts +42 -0
  575. package/src/deploy/platforms/static.ts +29 -0
  576. package/src/deploy/platforms/types.ts +112 -0
  577. package/src/deploy/platforms/vercel.ts +193 -0
  578. package/src/deploy/redirects.ts +30 -18
  579. package/src/deploy/robots.ts +3 -3
  580. package/src/deploy/rss.ts +2 -2
  581. package/src/deploy/sitemap.ts +2 -2
  582. package/src/deploy/vercel-negotiation.ts +2 -2
  583. package/src/eval/agents.ts +32 -1
  584. package/src/eval/findings.ts +19 -11
  585. package/src/eval/report.ts +10 -2
  586. package/src/markdown/external-links.ts +65 -0
  587. package/src/markdown/include.ts +45 -27
  588. package/src/markdown/index.ts +34 -9
  589. package/src/markdown/inline-code.ts +12 -6
  590. package/src/markdown/relative-links.ts +324 -0
  591. package/src/markdown/themes.ts +3 -3
  592. package/src/migrate/migrate.ts +149 -0
  593. package/src/openapi/parse.ts +64 -16
  594. package/src/openapi/proxy.ts +62 -10
  595. package/src/openapi/references.ts +147 -147
  596. package/src/openapi/render-mdx.ts +32 -10
  597. package/src/openapi/scalar.ts +15 -13
  598. package/src/openapi/sentence.ts +14 -0
  599. package/src/openapi/source.ts +37 -21
  600. package/src/openapi/spec-dependency-error.ts +15 -0
  601. package/src/reference/asyncapi.ts +83 -0
  602. package/src/reference/graphql.ts +89 -0
  603. package/src/reference/index.ts +40 -0
  604. package/src/reference/openapi.ts +83 -0
  605. package/src/reference/options.ts +201 -0
  606. package/src/reference/scalar.ts +136 -0
  607. package/src/reference/schema.ts +88 -0
  608. package/src/registry/eject.ts +109 -53
  609. package/src/search/adapters/algolia.ts +46 -0
  610. package/src/search/adapters/flexsearch.ts +29 -0
  611. package/src/search/adapters/index.ts +39 -0
  612. package/src/search/adapters/mixedbread.ts +39 -0
  613. package/src/search/adapters/orama-cloud.ts +51 -0
  614. package/src/search/adapters/orama.ts +24 -0
  615. package/src/search/adapters/pagefind.ts +27 -0
  616. package/src/search/adapters/registry.ts +131 -0
  617. package/src/search/adapters/types.ts +46 -0
  618. package/src/search/adapters/typesense.ts +57 -0
  619. package/src/search/build.ts +8 -4
  620. package/src/search/documents.ts +8 -2
  621. package/src/search/orama-index.ts +1 -1
  622. package/src/search/sync/algolia.ts +12 -11
  623. package/src/search/sync/index.ts +35 -20
  624. package/src/search/sync/orama-cloud.ts +12 -9
  625. package/src/search/sync/typesense.ts +15 -13
  626. package/src/sources/contentful.ts +63 -0
  627. package/src/sources/custom.ts +38 -0
  628. package/src/sources/filesystem.ts +51 -0
  629. package/src/sources/github-releases.ts +52 -0
  630. package/src/sources/index.ts +60 -0
  631. package/src/sources/mdx-remote.ts +76 -0
  632. package/src/sources/notion.ts +63 -0
  633. package/src/sources/obsidian.ts +38 -0
  634. package/src/sources/payload.ts +60 -0
  635. package/src/sources/registry.ts +182 -0
  636. package/src/sources/sanity.ts +66 -0
  637. package/src/sources/shared.ts +52 -0
  638. package/src/sources/strapi.ts +58 -0
  639. package/src/theme/entry.ts +24 -2
  640. package/src/translate/report.ts +40 -7
  641. package/src/upgrade/upgrade.ts +499 -0
  642. package/dist/cli/chunk-0qymqwzz.js +0 -164
  643. package/dist/cli/chunk-0qymqwzz.js.map +0 -15
  644. package/dist/cli/chunk-1jefwnfs.js.map +0 -48
  645. package/dist/cli/chunk-2mzebbbz.js +0 -69
  646. package/dist/cli/chunk-2z47ypj8.js.map +0 -11
  647. package/dist/cli/chunk-4x36ddpw.js.map +0 -11
  648. package/dist/cli/chunk-5093q3n7.js +0 -68
  649. package/dist/cli/chunk-5093q3n7.js.map +0 -10
  650. package/dist/cli/chunk-5qk08vmp.js.map +0 -11
  651. package/dist/cli/chunk-7s8hm3b6.js +0 -5347
  652. package/dist/cli/chunk-7s8hm3b6.js.map +0 -58
  653. package/dist/cli/chunk-8cjtbafj.js.map +0 -13
  654. package/dist/cli/chunk-97r59kpr.js +0 -381
  655. package/dist/cli/chunk-97r59kpr.js.map +0 -12
  656. package/dist/cli/chunk-ahnw3kxw.js.map +0 -15
  657. package/dist/cli/chunk-b27xqwn9.js.map +0 -10
  658. package/dist/cli/chunk-bf6bt1xt.js +0 -185
  659. package/dist/cli/chunk-bf6bt1xt.js.map +0 -11
  660. package/dist/cli/chunk-cjtn640a.js.map +0 -10
  661. package/dist/cli/chunk-ct47dqpx.js.map +0 -11
  662. package/dist/cli/chunk-esphfr8p.js +0 -107
  663. package/dist/cli/chunk-esphfr8p.js.map +0 -11
  664. package/dist/cli/chunk-ex56aa81.js +0 -1016
  665. package/dist/cli/chunk-ex56aa81.js.map +0 -13
  666. package/dist/cli/chunk-garjf5z9.js +0 -30
  667. package/dist/cli/chunk-garjf5z9.js.map +0 -10
  668. package/dist/cli/chunk-js7saxwm.js +0 -1045
  669. package/dist/cli/chunk-js7saxwm.js.map +0 -22
  670. package/dist/cli/chunk-k79xp7av.js.map +0 -39
  671. package/dist/cli/chunk-ps4m1xh4.js.map +0 -15
  672. package/dist/cli/chunk-q4rae3bg.js +0 -60
  673. package/dist/cli/chunk-q4rae3bg.js.map +0 -10
  674. package/dist/cli/chunk-rz9jmfhz.js +0 -108
  675. package/dist/cli/chunk-rz9jmfhz.js.map +0 -10
  676. package/dist/cli/chunk-vh9w1sgp.js +0 -73
  677. package/dist/cli/chunk-vh9w1sgp.js.map +0 -10
  678. package/dist/cli/chunk-vrfp10qk.js +0 -81
  679. package/dist/cli/chunk-vrfp10qk.js.map +0 -10
  680. package/dist/cli/chunk-yg63d42r.js.map +0 -34
  681. package/docs/advanced/api-reference.mdx +0 -240
  682. package/docs/configuration/ask-ai.mdx +0 -256
  683. package/docs/reference/cli.mdx +0 -197
  684. package/skills/blume-migrate/assets/oxfmt@0.55.0.patch +0 -20
  685. package/src/components/content/changelog-element.ts +0 -69
  686. package/src/search/providers.ts +0 -91
  687. /package/dist/cli/{chunk-vacwm2hv.js.map → chunk-27g6wdth.js.map} +0 -0
  688. /package/dist/cli/{chunk-dwgcp5sm.js.map → chunk-ce574jw2.js.map} +0 -0
  689. /package/dist/cli/{chunk-e7f42gdj.js.map → chunk-fxypxtvm.js.map} +0 -0
  690. /package/dist/cli/{chunk-nn13znc2.js.map → chunk-zxcczpyx.js.map} +0 -0
@@ -2,7 +2,7 @@
2
2
  import Ask from "blume:ask";
3
3
  import data from "blume:data";
4
4
  import { withBase } from "../islands/base-path.ts";
5
- import { isExternalUrl } from "../../core/base-path.ts";
5
+ import { NEW_TAB_ATTRS, newTabAttrs } from "../../core/new-tab.ts";
6
6
  import type { ComponentOverride } from "../../core/define-components.ts";
7
7
  import { EN_UI } from "../../core/i18n-ui.ts";
8
8
  import type { UIStrings } from "../../core/i18n-ui.ts";
@@ -34,13 +34,18 @@ interface Props {
34
34
  route: string;
35
35
  searchEnabled: boolean;
36
36
  /**
37
- * Whether the search modal offers an "Ask AI" hand-off. Defaults to whether
38
- * Ask AI is configured, so no page has to pass it; a layout can still opt a
37
+ * Active locale of the page, forwarded to the `Logo` slot so the brand link
38
+ * resolves to the locale's root like the tabs beside it.
39
+ */
40
+ locale?: string;
41
+ /**
42
+ * Whether the search modal offers a hand-off to the assistant. Defaults to whether
43
+ * the assistant is configured, so no page has to pass it; a layout can still opt a
39
44
  * shell out explicitly.
40
45
  */
41
- askEnabled?: boolean;
42
- /** Localized Ask AI strings for the active locale. */
43
- askStrings?: UIStrings["ask"];
46
+ assistantEnabled?: boolean;
47
+ /** Localized assistant strings for the active locale. */
48
+ assistantStrings?: UIStrings["assistant"];
44
49
  // The mobile menu button toggles the docs sidebar drawer; custom pages
45
50
  // without a sidebar (e.g. a landing page) pass `false` to hide it.
46
51
  hasSidebar?: boolean;
@@ -71,6 +76,12 @@ interface Props {
71
76
  * the whole header.
72
77
  */
73
78
  layout?: Record<string, ComponentOverride>;
79
+ /**
80
+ * Start the header see-through, with its chrome in white, so it can sit over
81
+ * a dark hero at the top of the page; it returns to the frosted bar as soon
82
+ * as the page scrolls (`PageLayout`'s `transparentHeader`).
83
+ */
84
+ transparent?: boolean;
74
85
  }
75
86
 
76
87
  const {
@@ -79,11 +90,12 @@ const {
79
90
  navigation,
80
91
  route,
81
92
  searchEnabled,
82
- // `config.ask` is null whenever Ask AI is off, so the header is the one place
93
+ locale,
94
+ // `config.assistant` is null whenever the assistant is off, so the header is the one place
83
95
  // that has to know — the Ask trigger below and the search modal's hand-off to
84
96
  // it both switch on this, and every page gets both for free.
85
- askEnabled = Boolean(data.config.ask),
86
- askStrings,
97
+ assistantEnabled = Boolean(data.config.assistant),
98
+ assistantStrings,
87
99
  hasSidebar = true,
88
100
  hasDrawer = true,
89
101
  searchStrings,
@@ -94,6 +106,7 @@ const {
94
106
  searchLocale,
95
107
  searchVersion = null,
96
108
  layout = {},
109
+ transparent = false,
97
110
  } = Astro.props;
98
111
 
99
112
  // Merge over the English defaults so a label missing from a translation (or
@@ -143,6 +156,12 @@ const ctaClass = [
143
156
  // for the swap (next-themes' disableTransitionOnChange trick), force a
144
157
  // synchronous style flush, then restore them on the next tick.
145
158
  //
159
+ // Both the theme toggle and the banner dismiss change the page first and only
160
+ // then remember the choice: localStorage throws where storage is blocked
161
+ // (Safari "Block All Cookies", sandboxed iframes), and a throw before the DOM
162
+ // change would leave the toggle dead with transitions switched off for good.
163
+ // There the choice simply isn't remembered.
164
+ //
146
165
  // The mobile drawer sits below the sticky header, but the banner above the
147
166
  // header has no fixed height (its text wraps), so the drawer's top offset can't
148
167
  // be static: on open we measure the header's on-screen bottom edge into
@@ -150,16 +169,31 @@ const ctaClass = [
150
169
  // so the offset can't drift while open, and re-measure when a banner dismiss or
151
170
  // a resize/rotation moves the header. Crossing into the desktop breakpoint
152
171
  // (64rem, Tailwind `lg`) closes the drawer so the scroll lock can't outlive it.
153
- const clickScript = `(()=>{const dr=()=>{const h=document.querySelector("[data-blume-header]");if(h){document.documentElement.style.setProperty("--blume-drawer-top",h.getBoundingClientRect().bottom+"px");}};document.addEventListener("click",(e)=>{const t=e.target.closest("[data-blume-theme-toggle]");if(t){const d=document.documentElement;const next=d.dataset.theme==="dark"?"light":"dark";const s=document.createElement("style");s.appendChild(document.createTextNode("*,*::before,*::after{transition:none!important}"));document.head.appendChild(s);d.dataset.theme=next;localStorage.setItem("blume-theme",next);window.getComputedStyle(d).opacity;setTimeout(()=>document.head.removeChild(s),1);}const n=e.target.closest("[data-blume-nav-toggle]");if(n){const d=document.documentElement;const open=d.toggleAttribute("data-blume-nav-open");if(open){dr();}}const b=e.target.closest("[data-blume-banner-dismiss]");if(b){const bar=b.closest("[data-blume-banner]");const k=bar&&bar.getAttribute("data-banner-key");if(k){localStorage.setItem("blume-banner:"+k,"1");}document.documentElement.setAttribute("data-blume-banner-hidden","");requestAnimationFrame(dr);}});addEventListener("resize",()=>{const d=document.documentElement;if(!d.hasAttribute("data-blume-nav-open")){return;}if(matchMedia("(min-width:64rem)").matches){d.removeAttribute("data-blume-nav-open");}else{dr();}});})();`;
172
+ // The rest of the drawer's behavior — the toggle's `aria-expanded`, Escape to
173
+ // close, and the inert page behind it — follows the same attribute from the
174
+ // layouts' drawer-inert.ts.
175
+ // Transparent header: flag the bar as scrolled once the page moves off the
176
+ // top so the stylesheet below can swap it back to the frosted look. `sync`
177
+ // re-queries the header each time, so the one listener a real load installs
178
+ // keeps working across client-router swaps (and no-ops on pages without it).
179
+ // The router re-runs this inline script on every swap into a transparent-header
180
+ // page, so it syncs that page each time but installs the listeners only once
181
+ // per real load, behind a window flag.
182
+ const transparentScript = `(()=>{const sync=()=>{const h=document.querySelector("[data-blume-header-transparent]");if(h){h.toggleAttribute("data-blume-header-scrolled",scrollY>8);}};sync();if(window.__blumeTransparentHeader){return;}window.__blumeTransparentHeader=true;addEventListener("scroll",sync,{passive:true});document.addEventListener("astro:after-swap",sync);})();`;
183
+
184
+ const clickScript = `(()=>{const dr=()=>{const h=document.querySelector("[data-blume-header]");if(h){document.documentElement.style.setProperty("--blume-drawer-top",h.getBoundingClientRect().bottom+"px");}};const save=(k,v)=>{try{localStorage.setItem(k,v);}catch{}};document.addEventListener("click",(e)=>{const t=e.target.closest("[data-blume-theme-toggle]");if(t){const d=document.documentElement;const next=d.dataset.theme==="dark"?"light":"dark";const s=document.createElement("style");s.appendChild(document.createTextNode("*,*::before,*::after{transition:none!important}"));document.head.appendChild(s);d.dataset.theme=next;window.getComputedStyle(d).opacity;setTimeout(()=>document.head.removeChild(s),1);save("blume-theme",next);}const n=e.target.closest("[data-blume-nav-toggle]");if(n){const d=document.documentElement;const open=d.toggleAttribute("data-blume-nav-open");if(open){dr();}}const b=e.target.closest("[data-blume-banner-dismiss]");if(b){document.documentElement.setAttribute("data-blume-banner-hidden","");requestAnimationFrame(dr);const bar=b.closest("[data-blume-banner]");const k=bar&&bar.getAttribute("data-banner-key");if(k){save("blume-banner:"+k,"1");}}});addEventListener("resize",()=>{const d=document.documentElement;if(!d.hasAttribute("data-blume-nav-open")){return;}if(matchMedia("(min-width:64rem)").matches){d.removeAttribute("data-blume-nav-open");}else{dr();}});})();`;
154
185
  ---
155
186
 
156
187
  <header
157
188
  class="sticky top-0 z-40 flex h-16 items-center gap-3 bg-background/90 px-4 backdrop-blur md:px-6"
158
189
  data-blume-header
190
+ data-blume-header-transparent={transparent ? "" : undefined}
159
191
  >
160
192
  {
161
193
  showNavToggle && (
162
194
  <button
195
+ aria-controls="blume-nav-drawer"
196
+ aria-expanded="false"
163
197
  aria-label={n.toggleNavigation}
164
198
  class={`${iconButton} lg:hidden`}
165
199
  data-blume-nav-toggle
@@ -169,7 +203,7 @@ const clickScript = `(()=>{const dr=()=>{const h=document.querySelector("[data-b
169
203
  </button>
170
204
  )
171
205
  }
172
- <LogoSlot logo={logo} site={site} />
206
+ <LogoSlot locale={locale} logo={logo} site={site} />
173
207
  {
174
208
  navigation.selectors.length > 0 && (
175
209
  <div class="flex items-center gap-1.5">
@@ -216,7 +250,7 @@ const clickScript = `(()=>{const dr=()=>{const h=document.querySelector("[data-b
216
250
  {
217
251
  searchEnabled && (
218
252
  <SearchSlot
219
- askEnabled={askEnabled}
253
+ assistantEnabled={assistantEnabled}
220
254
  locale={searchLocale}
221
255
  navigation={navigation}
222
256
  popularPages={data.config.search.popular}
@@ -229,27 +263,22 @@ const clickScript = `(()=>{const dr=()=>{const h=document.querySelector("[data-b
229
263
  {
230
264
  /* Plain links first, then the one filled button: the reader's eye lands
231
265
  on the button because nothing beside it competes. */
232
- (navigation.actions ?? []).map((action) => {
233
- const external = isExternalUrl(action.href);
234
- return (
235
- <a
236
- class={actionClass}
237
- href={withBase(action.href)}
238
- rel={external ? "noreferrer" : undefined}
239
- target={external ? "_blank" : undefined}
240
- >
241
- {action.label}
242
- </a>
243
- );
244
- })
266
+ (navigation.actions ?? []).map((action) => (
267
+ <a
268
+ class={actionClass}
269
+ href={withBase(action.href)}
270
+ {...newTabAttrs(action.href)}
271
+ >
272
+ {action.label}
273
+ </a>
274
+ ))
245
275
  }
246
276
  {
247
277
  navigation.cta && (
248
278
  <a
249
279
  class={ctaClass}
250
280
  href={withBase(navigation.cta.href)}
251
- rel={isExternalUrl(navigation.cta.href) ? "noreferrer" : undefined}
252
- target={isExternalUrl(navigation.cta.href) ? "_blank" : undefined}
281
+ {...newTabAttrs(navigation.cta.href)}
253
282
  >
254
283
  {navigation.cta.label}
255
284
  <Icon name="chevron-right" size={14} />
@@ -262,8 +291,7 @@ const clickScript = `(()=>{const dr=()=>{const h=document.querySelector("[data-b
262
291
  aria-label={n.githubRepository}
263
292
  class={iconButton}
264
293
  href={navigation.repoUrl}
265
- rel="noreferrer"
266
- target="_blank"
294
+ {...NEW_TAB_ATTRS}
267
295
  >
268
296
  <svg
269
297
  aria-hidden="true"
@@ -286,7 +314,42 @@ const clickScript = `(()=>{const dr=()=>{const h=document.querySelector("[data-b
286
314
  <span class="inline-flex dark:hidden"><Icon name="sun" size={18} /></span>
287
315
  <span class="hidden dark:inline-flex"><Icon name="moon" size={18} /></span>
288
316
  </button>
289
- {askEnabled && <Ask strings={askStrings} />}
317
+ {assistantEnabled && <Ask strings={assistantStrings} />}
290
318
  </div>
291
319
  </header>
292
320
  <script is:inline set:html={clickScript} />
321
+ {transparent && <script is:inline set:html={transparentScript} />}
322
+
323
+ <style is:global>
324
+ /* Transparent header (PageLayout `transparentHeader`): at the top of the
325
+ page the bar has no background and its chrome renders in white so it can
326
+ sit over a dark hero; once scrolled it is the frosted bar again. The color
327
+ tokens are re-declared on the header so every utility inside follows, and
328
+ the search dialog gets the page's own values back so it isn't inverted. */
329
+ :root {
330
+ --blume-header-background: var(--blume-background);
331
+ --blume-header-foreground: var(--blume-foreground);
332
+ --blume-header-muted: var(--blume-muted);
333
+ --blume-header-muted-foreground: var(--blume-muted-foreground);
334
+ --blume-header-border: var(--blume-border);
335
+ }
336
+ [data-blume-header-transparent] {
337
+ transition: background-color 200ms;
338
+ }
339
+ [data-blume-header-transparent]:not([data-blume-header-scrolled]) {
340
+ --blume-background: transparent;
341
+ --blume-foreground: #fff;
342
+ --blume-muted: rgb(255 255 255 / 0.12);
343
+ --blume-muted-foreground: rgb(255 255 255 / 0.75);
344
+ --blume-border: rgb(255 255 255 / 0.25);
345
+ background-color: transparent;
346
+ backdrop-filter: none;
347
+ }
348
+ [data-blume-header-transparent]:not([data-blume-header-scrolled]) dialog {
349
+ --blume-background: var(--blume-header-background);
350
+ --blume-foreground: var(--blume-header-foreground);
351
+ --blume-muted: var(--blume-header-muted);
352
+ --blume-muted-foreground: var(--blume-header-muted-foreground);
353
+ --blume-border: var(--blume-header-border);
354
+ }
355
+ </style>
@@ -11,6 +11,9 @@ interface Props {
11
11
 
12
12
  const { options, label, untranslatedLabel } = Astro.props;
13
13
  const current = options.find((option) => option.current);
14
+ // The accessible name carries the visible language name too (WCAG 2.5.3,
15
+ // label in name), so a voice-control user can say "English" to open it.
16
+ const summaryLabel = current ? `${label}: ${current.label}` : label;
14
17
 
15
18
  const iconButton =
16
19
  "inline-flex h-9 cursor-pointer items-center gap-1.5 rounded-full border border-border bg-background px-3 text-muted-foreground text-sm transition-colors hover:border-foreground hover:text-foreground";
@@ -22,7 +25,7 @@ const menuRowClass =
22
25
  options.length > 1 && (
23
26
  <details class="group relative" data-blume-dropdown>
24
27
  <summary
25
- aria-label={label}
28
+ aria-label={summaryLabel}
26
29
  class={`${iconButton} list-none [&::-webkit-details-marker]:hidden`}
27
30
  >
28
31
  <Icon name="globe" size={16} />
@@ -1,17 +1,26 @@
1
1
  ---
2
+ import data from "blume:data";
2
3
  import { withBase } from "../islands/base-path.ts";
3
4
  // The site brand: the logo mark (inline SVG so a `currentColor` mark follows the
4
5
  // theme, or light/dark `<img>` variants) followed by the site title, wrapped in
5
6
  // a link to the brand href. Extracted from the header so it can be replaced on
6
7
  // its own through the `Logo` layout slot (`defineComponents({ layout: { Logo } })`).
7
8
  import type { BlumeLogo } from "../../core/data.ts";
9
+ import { localizeInternalPath } from "../../core/i18n.ts";
8
10
 
9
11
  interface Props {
10
12
  site: { title: string };
11
13
  logo?: BlumeLogo | null;
14
+ /**
15
+ * Active locale of the page. The brand link follows it the way the header
16
+ * tabs do (`/` -> `/en` under a prefixed locale), so clicking the logo never
17
+ * drops a reader out of the language they were reading. Absent, the link is
18
+ * emitted as configured.
19
+ */
20
+ locale?: string;
12
21
  }
13
22
 
14
- const { site, logo } = Astro.props;
23
+ const { site, logo, locale } = Astro.props;
15
24
 
16
25
  // The logo is resolved upstream: an inline `svg` (so a currentColor mark follows
17
26
  // the theme) or image URL(s) for an <img>.
@@ -21,7 +30,19 @@ const logoDark = logo?.dark ?? logo?.light;
21
30
  const logoLightDimensions = logo?.dimensions?.light;
22
31
  const logoDarkDimensions = logo?.dimensions?.dark ?? logo?.dimensions?.light;
23
32
  const logoAlt = logo?.alt ?? "";
24
- const brandHref = logo?.href ?? "/";
33
+ // The configured `logo.href` (default `/`) is authored in the default locale's
34
+ // path space, like a tab path; localize it for the page's locale. The locale's
35
+ // navigation carries it resolved against the routes the build serves (a custom
36
+ // home page exists only at `/`, so it isn't moved to `/fr`). External and
37
+ // protocol-relative hrefs pass through, and `withBase` layers the deployment
38
+ // base on afterwards, so the locale prefix sits inside the base like every
39
+ // other localized route.
40
+ const configuredHref = logo?.href ?? "/";
41
+ const brandHref =
42
+ locale === undefined
43
+ ? configuredHref
44
+ : (data.navigationByLocale[locale]?.brandHref ??
45
+ localizeInternalPath(configuredHref, locale, data.config.i18n));
25
46
  // Wordmark beside the mark: the configured `logo.text`, or the site title when
26
47
  // it's omitted. An explicit `""` hides it (an image-only logo whose mark already
27
48
  // carries the wordmark).
@@ -33,6 +33,12 @@ const active =
33
33
  .toSorted((a, b) => b.path.length - a.path.length)[0] ??
34
34
  selector.items[0];
35
35
 
36
+ // The active label rides in the trigger on every screen when it's short (a
37
+ // version like "v2" or "Latest"): on a phone a bare chevron doesn't say what
38
+ // it switches. Longer product names still collapse to the icon there.
39
+ const label = active?.label ?? selector.label;
40
+ const labelClass = label.length <= 6 ? undefined : "max-sm:hidden";
41
+
36
42
  const iconButton =
37
43
  "inline-flex h-9 cursor-pointer items-center gap-1.5 rounded-full border border-border bg-background px-3 text-muted-foreground text-sm transition-colors hover:border-foreground hover:text-foreground";
38
44
  const menuRowClass =
@@ -47,7 +53,7 @@ const menuRowClass =
47
53
  class={`${iconButton} list-none [&::-webkit-details-marker]:hidden`}
48
54
  >
49
55
  {active?.icon && <Icon name={active.icon} size={16} />}
50
- <span class="max-sm:hidden">{active?.label ?? selector.label}</span>
56
+ <span class={labelClass}>{label}</span>
51
57
  <Icon
52
58
  class="transition-transform group-open:rotate-180"
53
59
  name="chevron-down"
@@ -55,7 +61,8 @@ const menuRowClass =
55
61
  />
56
62
  </summary>
57
63
  <div
58
- class={`absolute z-50 mt-2 min-w-56 rounded-blume border border-border bg-background p-1 shadow-xl ${align === "end" ? "end-0" : "start-0"}`}
64
+ class={`absolute z-50 mt-2 min-w-56 max-w-[calc(100vw-1rem)] rounded-blume border border-border bg-background p-1 shadow-xl ${align === "end" ? "end-0" : "start-0"}`}
65
+ data-blume-dropdown-panel
59
66
  >
60
67
  {selector.items.map((item) => (
61
68
  <a
@@ -89,8 +96,12 @@ const menuRowClass =
89
96
 
90
97
  <script>
91
98
  import { installDropdownDismiss } from "../dropdown-dismiss.ts";
99
+ import { installDropdownClamp } from "./dropdown-clamp.ts";
92
100
 
93
101
  // Outside-click / Escape / focus-out dismissal, shared with the language
94
102
  // switcher and the page actions.
95
103
  installDropdownDismiss();
104
+ // On a phone the trigger sits mid-header, so an edge-anchored panel can
105
+ // run off the screen; shift it back inside when it opens.
106
+ installDropdownClamp();
96
107
  </script>
@@ -8,6 +8,7 @@ import { withBase } from "../islands/base-path.ts";
8
8
  import Self from "./NavTree.astro";
9
9
  import NavTreeCache from "./NavTreeCache.astro";
10
10
  import NavTreeScript from "./NavTreeScript.astro";
11
+ import { isGroupRowCurrent } from "./nav-utils.ts";
11
12
 
12
13
  interface PagePanel {
13
14
  id: string;
@@ -180,7 +181,11 @@ const initialId =
180
181
  <Icon class="rtl:-scale-x-100" name="arrow-left" size={16} />
181
182
  </button>
182
183
  <a
183
- aria-current={panel.route === currentRoute ? "page" : undefined}
184
+ aria-current={
185
+ isGroupRowCurrent(panel.node, currentRoute)
186
+ ? "page"
187
+ : undefined
188
+ }
184
189
  class="flex-1 truncate rounded-[0.65rem] px-1 py-1 font-semibold text-foreground text-sm transition-colors hover:bg-muted"
185
190
  href={withBase(panel.route)}
186
191
  >
@@ -329,7 +334,9 @@ const initialId =
329
334
  {item.route ? (
330
335
  <a
331
336
  aria-current={
332
- item.route === currentRoute ? "page" : undefined
337
+ isGroupRowCurrent(item, currentRoute)
338
+ ? "page"
339
+ : undefined
333
340
  }
334
341
  class="blume-nav-summary-link"
335
342
  href={withBase(item.route)}
@@ -412,7 +419,9 @@ const initialId =
412
419
  <p class="blume-nav-heading">
413
420
  {item.route ? (
414
421
  <a
415
- aria-current={item.route === currentRoute ? "page" : undefined}
422
+ aria-current={
423
+ isGroupRowCurrent(item, currentRoute) ? "page" : undefined
424
+ }
416
425
  class="blume-nav-heading-link"
417
426
  href={withBase(item.route)}
418
427
  >
@@ -77,7 +77,12 @@
77
77
  }
78
78
  const slot = deferredIn(details.querySelector(":scope > [data-nav-src]"));
79
79
  if (slot) {
80
- void fillDeferred(slot);
80
+ // A fragment that can't load (offline, a deploy that dropped it)
81
+ // closes the group again instead of leaving it open and empty; the
82
+ // slot keeps its `data-nav-src`, so the next open retries.
83
+ fillDeferred(slot).catch(() => {
84
+ details.open = false;
85
+ });
81
86
  }
82
87
  },
83
88
  true
@@ -159,8 +164,17 @@
159
164
  if (!next) {
160
165
  return;
161
166
  }
162
- // A deferred panel loads its contents before it slides in.
163
- await fillDeferred(next);
167
+ // A deferred panel loads its contents before it slides in. When its
168
+ // fragment can't load (offline, a deploy that dropped it), the drill-in
169
+ // follows the section's own page link — the panel's title — instead of
170
+ // leaving the click dead. A section with no page stays put, and the next
171
+ // drill-in retries the fetch.
172
+ try {
173
+ await fillDeferred(next);
174
+ } catch {
175
+ next.querySelector<HTMLAnchorElement>("a[href]")?.click();
176
+ return;
177
+ }
164
178
  const prev = this.active;
165
179
  if (prev === next) {
166
180
  return;
@@ -1,6 +1,7 @@
1
1
  ---
2
2
  import { EN_UI } from "../../core/i18n-ui.ts";
3
3
  import type { UIStrings } from "../../core/i18n-ui.ts";
4
+ import { NEW_TAB_ATTRS } from "../../core/new-tab.ts";
4
5
  import { openInChatProviders } from "../../core/open-in-chat.ts";
5
6
  import type { OpenInChatProvider } from "../../core/open-in-chat.ts";
6
7
  import { GITHUB_MARK } from "../github-mark.ts";
@@ -112,7 +113,7 @@ const menuRowClass =
112
113
  >
113
114
  {
114
115
  editUrl && (
115
- <a class={rowClass} href={editUrl} rel="noreferrer" target="_blank">
116
+ <a class={rowClass} href={editUrl} {...NEW_TAB_ATTRS}>
116
117
  <span set:html={GITHUB_LOGO} />
117
118
  {a.edit}
118
119
  </a>
@@ -192,8 +193,7 @@ const menuRowClass =
192
193
  <a
193
194
  class={menuRowClass}
194
195
  data-open-in={provider.key}
195
- rel="noreferrer"
196
- target="_blank"
196
+ {...NEW_TAB_ATTRS}
197
197
  >
198
198
  <span set:html={LOGOS[provider.key]} />
199
199
  <span class="flex-1">{provider.label}</span>
@@ -34,7 +34,12 @@ const buttonClass =
34
34
  {f.no}
35
35
  </button>
36
36
  </div>
37
- <p class="hidden text-muted-foreground text-sm" data-feedback-thanks>
37
+ <p
38
+ class="hidden text-muted-foreground text-sm focus:outline-none"
39
+ data-feedback-thanks
40
+ role="status"
41
+ tabindex="-1"
42
+ >
38
43
  {f.thanks}
39
44
  </p>
40
45
  </section>
@@ -61,6 +66,11 @@ const buttonClass =
61
66
  });
62
67
  actions?.classList.add("hidden");
63
68
  thanks?.classList.remove("hidden");
69
+ // The clicked button just disappeared under focus; hand focus to the
70
+ // (live) thanks line so the reader isn't dropped at the top of the page.
71
+ if (thanks instanceof HTMLElement) {
72
+ thanks.focus();
73
+ }
64
74
  });
65
75
  }
66
76
  };
@@ -23,10 +23,12 @@ import type {
23
23
  BlumeDataConfig,
24
24
  BlumeFavicon,
25
25
  BlumeLogo,
26
+ DiscoveryHeadLinks,
26
27
  } from "../../core/data.ts";
27
28
  import data from "blume:data";
28
29
  import { EN_UI } from "../../core/i18n-ui.ts";
29
30
  import type { UIStrings } from "../../core/i18n-ui.ts";
31
+ import { NEW_TAB_HINT_ID } from "../../core/new-tab.ts";
30
32
  import type { FontHead } from "../../theme/fonts.ts";
31
33
  import type { LocaleSwitchOption, Navigation } from "../../core/types.ts";
32
34
  import {
@@ -36,13 +38,14 @@ import {
36
38
  } from "../../og/dimensions.ts";
37
39
  import { buildStructuredData } from "../../seo/jsonld.ts";
38
40
  import { normalizeXHandle } from "../../seo/x-handle.ts";
39
- import { withBase } from "../islands/base-path.ts";
41
+ import { stripBase, withBase } from "../islands/base-path.ts";
40
42
  import { pageDirection, pageLocale } from "./page-locale.ts";
41
43
  import { searchLocaleFor } from "./search-locale.ts";
42
44
  import "blume:theme";
43
45
  import { ClientRouter } from "astro:transitions";
44
46
  import Analytics from "./Analytics.astro";
45
47
  import Banner from "./Banner.astro";
48
+ import DiscoveryLinks from "./DiscoveryLinks.astro";
46
49
  import Favicon from "./Favicon.astro";
47
50
  import Fonts from "./Fonts.astro";
48
51
  import {
@@ -65,18 +68,28 @@ interface Props {
65
68
  /**
66
69
  * Title/description/route for `<title>`, `og:*`, and the header active link.
67
70
  * Unlike RootLayout, `title` is used verbatim (no `- siteTitle` suffix), since
68
- * marketing pages usually set their own complete document title.
71
+ * marketing pages usually set their own complete document title. An omitted
72
+ * `route` is read from the request URL (base-less), so a page that never
73
+ * passes one still gets its own canonical, OG card, and Markdown mirror
74
+ * rather than the homepage's.
69
75
  */
70
76
  page?: { title?: string; description?: string; route?: string };
71
77
  themeMode: "system" | "light" | "dark";
72
78
  fontCssVars?: (string | FontHead)[];
73
79
  searchEnabled: boolean;
74
80
  /**
75
- * Opt this page out of the header's Ask AI trigger. Defaults to whether Ask
76
- * AI is configured, so a custom page gets the same trigger the docs pages have
81
+ * Opt this page out of the header's assistant trigger. Defaults to whether the
82
+ * assistant is configured, so a custom page gets the same trigger the docs pages have
77
83
  * without wiring anything up.
78
84
  */
79
- askEnabled?: boolean;
85
+ assistantEnabled?: boolean;
86
+ /**
87
+ * Start the header see-through, with its chrome in white, so it can sit over
88
+ * a dark hero at the top of the page; it returns to the frosted bar as soon
89
+ * as the page scrolls. The hero has to run under the header for this to
90
+ * show: pull it up by the header's height (`-mt-16`) and pad its top.
91
+ */
92
+ transparentHeader?: boolean;
80
93
  /**
81
94
  * Absolute site URL (`data.config.site`). When set, `canonical` and the
82
95
  * generated `og:image` for this route are derived from it automatically;
@@ -105,6 +118,14 @@ interface Props {
105
118
  * matching RootLayout.
106
119
  */
107
120
  structuredDataEnabled?: boolean;
121
+ /**
122
+ * Which agent-discovery resources exist, advertised as `describedby` head
123
+ * links plus the AI Catalog/ARD pair — the same `DiscoveryLinks` partial
124
+ * RootLayout renders. A custom page gets this from the `blume:data` snapshot
125
+ * without wiring anything up; pass it to override, or `null` to drop the
126
+ * links.
127
+ */
128
+ discovery?: DiscoveryHeadLinks | null;
108
129
  /**
109
130
  * X (Twitter) attribution (`data.config.x`): the site's account and an author
110
131
  * handle, emitted as `twitter:site`/`twitter:creator`.
@@ -142,7 +163,8 @@ const {
142
163
  themeMode,
143
164
  fontCssVars,
144
165
  searchEnabled,
145
- askEnabled,
166
+ assistantEnabled,
167
+ transparentHeader,
146
168
  siteUrl,
147
169
  ogEnabled,
148
170
  ogImage,
@@ -150,6 +172,7 @@ const {
150
172
  ogImageSize,
151
173
  canonical,
152
174
  structuredDataEnabled,
175
+ discovery = data.config.discovery,
153
176
  x,
154
177
  noindex,
155
178
  locale: localeProp,
@@ -180,14 +203,30 @@ const navStrings = { ...EN_UI.nav, ...strings.nav };
180
203
  const searchLocale = searchLocaleFor(data.config.i18n, locale);
181
204
  const pageTitle = page?.title ?? site.title;
182
205
  const description = page?.description ?? site.description;
183
- const route = page?.route ?? "/";
206
+ // An omitted `page.route` falls back to the request pathname with the
207
+ // deployment base stripped, not to `/`: a non-root page that copies the
208
+ // homepage example would otherwise claim the homepage's canonical, OG card,
209
+ // and `/index.md` mirror. `trailingSlash: "never"` keeps the pathname bare.
210
+ const route =
211
+ page?.route ??
212
+ stripBase(import.meta.env.BASE_URL ?? "/", Astro.url.pathname);
184
213
  const activeTab = currentTabForRoute(navigation.tabs, route, navigation.root);
185
214
 
215
+ // A custom page has no raw-Markdown mirror — except the homepage: `/index.md`
216
+ // always exists (see `markdownRoutePaths`; a landing page gets the synthesized
217
+ // llms.txt index), and the homepage HTTP `Link` header already advertises it,
218
+ // so the head is its HTML counterpart on hosts that can't set that header.
219
+ // Advertising a mirror for any other custom route would 404.
220
+ const markdownMirror = route === "/" ? withBase("/index.md") : null;
221
+
186
222
  // Derive canonical + og:image from the site URL the same way the catch-all does
187
223
  // for content pages, so a custom page gets both for free. The matching OG card
188
224
  // (`/og/<slug>.png`, `index` for the home) is generated for static custom pages
189
- // when `og.enabled`. Explicit props still win.
225
+ // when `og.enabled` — not for a dynamic route (`[param]`), whose concrete URLs
226
+ // can't be enumerated up front, so such a page advertises no generated card
227
+ // rather than one that 404s. Explicit props still win.
190
228
  const ogSlug = route === "/" ? "index" : route.slice(1);
229
+ const ogCardGenerated = !Astro.routePattern.includes("[");
191
230
  // Absolute URLs carry the deployment base too (`site + base + route`), since the
192
231
  // page is served under it. `withBase` handles a root base as a no-op.
193
232
  const basedRoute = withBase(route);
@@ -211,7 +250,7 @@ const absolutizeOgImage = (value: string): string =>
211
250
  value.startsWith("/") && siteBase ? `${siteBase}${withBase(value)}` : value;
212
251
  const resolvedOgImage = ogImage
213
252
  ? absolutizeOgImage(ogImage)
214
- : ogEnabled && siteBase
253
+ : ogEnabled && ogCardGenerated && siteBase
215
254
  ? `${siteBase}${encodeURI(withBase(`/og/${ogSlug}.png`))}`
216
255
  : null;
217
256
  // Only the generated card has a known size and format, so its dimensions are
@@ -321,6 +360,9 @@ createIconSprite(Astro.locals);
321
360
  {description && <meta content={description} name="twitter:description" />}
322
361
  {xSite && <meta content={xSite} name="twitter:site" />}
323
362
  {xCreator && <meta content={xCreator} name="twitter:creator" />}
363
+ {/* Agent discovery on every page, so a custom page built on PageLayout is
364
+ covered without passing a prop, the same way the docs pages are. */}
365
+ <DiscoveryLinks discovery={discovery} markdownMirror={markdownMirror} />
324
366
  {
325
367
  structuredDataJson && (
326
368
  <script
@@ -343,11 +385,14 @@ createIconSprite(Astro.locals);
343
385
  class="absolute start-[-999px] top-0 z-[100] bg-accent px-4 py-2 text-accent-foreground focus:start-0"
344
386
  href="#blume-content">{strings.page.skipToContent}</a
345
387
  >
388
+ {/* Every new-tab link's aria-describedby target (see core/new-tab.ts). */}
389
+ <span hidden id={NEW_TAB_HINT_ID}>{navStrings.opensInNewTab}</span>
346
390
  <Banner banner={banner} strings={strings.banner} />
347
391
  <Header
348
- askEnabled={askEnabled}
349
- askStrings={strings.ask}
392
+ assistantEnabled={assistantEnabled}
393
+ assistantStrings={strings.assistant}
350
394
  hasSidebar={false}
395
+ locale={locale}
351
396
  localeSwitch={localeSwitch}
352
397
  logo={logo}
353
398
  navStrings={navStrings}
@@ -358,6 +403,7 @@ createIconSprite(Astro.locals);
358
403
  searchStrings={strings.search}
359
404
  site={site}
360
405
  switcherStrings={strings.languageSwitcher}
406
+ transparent={transparentHeader}
361
407
  />
362
408
  <main id="blume-content"><slot /></main>
363
409
  <slot name="footer" />
@@ -374,6 +420,7 @@ createIconSprite(Astro.locals);
374
420
  aria-label={navStrings.navigation}
375
421
  class="fixed top-[var(--blume-drawer-top,4rem)] start-0 z-[35] h-[calc(100dvh-var(--blume-drawer-top,4rem))] w-64 max-w-[80vw] -translate-x-[105%] overflow-y-auto border-border border-e bg-background px-5 pt-4 pb-6 transition-transform rtl:translate-x-[105%] [:where([data-blume-nav-open])_&]:translate-x-0! lg:hidden"
376
422
  data-blume-nav-drawer
423
+ id="blume-nav-drawer"
377
424
  >
378
425
  <nav aria-label={navStrings.sections}>
379
426
  <ul class="m-0 list-none p-0">