blume 1.7.3 → 2.0.0

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 (673) hide show
  1. package/AGENTS.md +19 -0
  2. package/CHANGELOG.md +217 -0
  3. package/README.md +34 -21
  4. package/dist/cli/chunk-11j0384y.js +148 -0
  5. package/dist/cli/chunk-11j0384y.js.map +10 -0
  6. package/dist/cli/{chunk-rqy0s5wh.js → chunk-1w8dp3qb.js} +17 -16
  7. package/dist/cli/{chunk-rqy0s5wh.js.map → chunk-1w8dp3qb.js.map} +3 -3
  8. package/dist/cli/chunk-2q1dwty4.js +75 -0
  9. package/dist/cli/chunk-2q1dwty4.js.map +11 -0
  10. package/dist/cli/chunk-41za066z.js +122 -0
  11. package/dist/cli/chunk-41za066z.js.map +11 -0
  12. package/dist/cli/chunk-5a2z0198.js +133 -0
  13. package/dist/cli/chunk-5a2z0198.js.map +10 -0
  14. package/dist/cli/{chunk-5g0w1e2c.js → chunk-6k8vp3ta.js} +25 -13
  15. package/dist/cli/{chunk-5g0w1e2c.js.map → chunk-6k8vp3ta.js.map} +4 -4
  16. package/dist/cli/{chunk-5qk08vmp.js → chunk-79njf86q.js} +133 -54
  17. package/dist/cli/chunk-79njf86q.js.map +11 -0
  18. package/dist/cli/{chunk-e7f42gdj.js → chunk-7ez8ny0t.js} +2 -2
  19. package/dist/cli/{chunk-nn13znc2.js → chunk-88cpgt6h.js} +1 -1
  20. package/dist/cli/chunk-a9kptbw5.js +361 -0
  21. package/dist/cli/chunk-a9kptbw5.js.map +14 -0
  22. package/dist/cli/chunk-abh8yjkn.js +31 -0
  23. package/dist/cli/chunk-abh8yjkn.js.map +10 -0
  24. package/dist/cli/chunk-b5aj94ah.js +91 -0
  25. package/dist/cli/chunk-b5aj94ah.js.map +10 -0
  26. package/dist/cli/{chunk-b27xqwn9.js → chunk-bctazmbk.js} +9 -5
  27. package/dist/cli/chunk-bctazmbk.js.map +10 -0
  28. package/dist/cli/chunk-beat36xx.js +279 -0
  29. package/dist/cli/chunk-beat36xx.js.map +10 -0
  30. package/dist/cli/chunk-bnbmcwfb.js +145 -0
  31. package/dist/cli/chunk-bnbmcwfb.js.map +11 -0
  32. package/dist/cli/{chunk-0xjyb285.js → chunk-bw22s759.js} +15 -5
  33. package/dist/cli/{chunk-0xjyb285.js.map → chunk-bw22s759.js.map} +4 -4
  34. package/dist/cli/chunk-by2290sx.js +39 -0
  35. package/dist/cli/chunk-by2290sx.js.map +10 -0
  36. package/dist/cli/chunk-d1tadaw7.js +79 -0
  37. package/dist/cli/chunk-d1tadaw7.js.map +10 -0
  38. package/dist/cli/{chunk-ahnw3kxw.js → chunk-d80hr03s.js} +24 -19
  39. package/dist/cli/chunk-d80hr03s.js.map +15 -0
  40. package/dist/cli/chunk-ernrthtr.js +97 -0
  41. package/dist/cli/chunk-ernrthtr.js.map +10 -0
  42. package/dist/cli/chunk-f2972sbt.js +374 -0
  43. package/dist/cli/chunk-f2972sbt.js.map +10 -0
  44. package/dist/cli/{chunk-vacwm2hv.js → chunk-f7t03s3g.js} +2 -2
  45. package/dist/cli/{chunk-2z47ypj8.js → chunk-fa25z98p.js} +16 -3
  46. package/dist/cli/chunk-fa25z98p.js.map +11 -0
  47. package/dist/cli/{chunk-cjtn640a.js → chunk-fh5hj5jt.js} +43 -20
  48. package/dist/cli/chunk-fh5hj5jt.js.map +10 -0
  49. package/dist/cli/{chunk-yg63d42r.js → chunk-j8mw0za6.js} +86 -57
  50. package/dist/cli/chunk-j8mw0za6.js.map +35 -0
  51. package/dist/cli/{chunk-ct47dqpx.js → chunk-jts8mvcz.js} +67 -7
  52. package/dist/cli/{chunk-2mzebbbz.js.map → chunk-jts8mvcz.js.map} +6 -4
  53. package/dist/cli/{chunk-ps4m1xh4.js → chunk-mnqj32sj.js} +505 -542
  54. package/dist/cli/chunk-mnqj32sj.js.map +12 -0
  55. package/dist/cli/{chunk-8cjtbafj.js → chunk-mwt1k8n7.js} +100 -374
  56. package/dist/cli/chunk-mwt1k8n7.js.map +10 -0
  57. package/dist/cli/chunk-nk3ts2xk.js +51 -0
  58. package/dist/cli/chunk-nk3ts2xk.js.map +10 -0
  59. package/dist/cli/{chunk-3r45185y.js → chunk-pat2zzwc.js} +9 -11
  60. package/dist/cli/{chunk-3r45185y.js.map → chunk-pat2zzwc.js.map} +2 -2
  61. package/dist/cli/chunk-pnnvybbk.js +176 -0
  62. package/dist/cli/chunk-pnnvybbk.js.map +11 -0
  63. package/dist/cli/{chunk-4x36ddpw.js → chunk-sqn5t4q0.js} +81 -87
  64. package/dist/cli/chunk-sqn5t4q0.js.map +10 -0
  65. package/dist/cli/{chunk-bvwwhd84.js → chunk-tzne8qfq.js} +15 -15
  66. package/dist/cli/{chunk-bvwwhd84.js.map → chunk-tzne8qfq.js.map} +1 -1
  67. package/dist/cli/chunk-xaz13gwg.js +12449 -0
  68. package/dist/cli/chunk-xaz13gwg.js.map +182 -0
  69. package/dist/cli/chunk-y3e45rc8.js +102 -0
  70. package/dist/cli/chunk-y3e45rc8.js.map +10 -0
  71. package/dist/cli/chunk-z01ze5c1.js +261 -0
  72. package/dist/cli/chunk-z01ze5c1.js.map +10 -0
  73. package/dist/cli/{chunk-k79xp7av.js → chunk-z1f5arsg.js} +219 -689
  74. package/dist/cli/chunk-z1f5arsg.js.map +36 -0
  75. package/dist/cli/{chunk-1jefwnfs.js → chunk-zg2gtj10.js} +1076 -2592
  76. package/dist/cli/chunk-zg2gtj10.js.map +35 -0
  77. package/dist/cli/{chunk-dwgcp5sm.js → chunk-zxccj738.js} +1 -1
  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.d.ts +368 -0
  85. package/dist/types/ai/changelog-markdown.d.ts +2 -0
  86. package/dist/types/ai/component-markdown.d.ts +2 -2
  87. package/dist/types/ai/index.d.ts +24 -0
  88. package/dist/types/ai/link-headers.d.ts +24 -0
  89. package/dist/types/ai/llms.d.ts +25 -0
  90. package/dist/types/ai/markdown.d.ts +45 -0
  91. package/dist/types/ai/mcp/discovery.d.ts +68 -0
  92. package/dist/types/ai/mcp/tools.d.ts +16 -0
  93. package/dist/types/ai/openapi-components.d.ts +43 -0
  94. package/dist/types/ai/relative-links.d.ts +26 -0
  95. package/dist/types/ai/serializers.d.ts +15 -0
  96. package/dist/types/ai/skills.d.ts +42 -0
  97. package/dist/types/ai/tar.d.ts +25 -0
  98. package/dist/types/ai/visibility.d.ts +17 -0
  99. package/dist/types/ai/web-bot-auth.d.ts +16 -0
  100. package/dist/types/analytics/adobe.d.ts +35 -0
  101. package/dist/types/analytics/amplitude.d.ts +50 -0
  102. package/dist/types/analytics/clarity.d.ts +31 -0
  103. package/dist/types/analytics/clearbit.d.ts +29 -0
  104. package/dist/types/analytics/cloudflare.d.ts +41 -0
  105. package/dist/types/analytics/fathom.d.ts +41 -0
  106. package/dist/types/analytics/google-analytics.d.ts +44 -0
  107. package/dist/types/analytics/google-tag-manager.d.ts +40 -0
  108. package/dist/types/analytics/head.d.ts +30 -0
  109. package/dist/types/analytics/heap.d.ts +40 -0
  110. package/dist/types/analytics/hightouch.d.ts +44 -0
  111. package/dist/types/analytics/hotjar.d.ts +33 -0
  112. package/dist/types/analytics/index.d.ts +61 -0
  113. package/dist/types/analytics/inline.d.ts +17 -0
  114. package/dist/types/analytics/logrocket.d.ts +44 -0
  115. package/dist/types/analytics/mixpanel.d.ts +67 -0
  116. package/dist/types/analytics/pirsch.d.ts +42 -0
  117. package/dist/types/analytics/plausible.d.ts +56 -0
  118. package/dist/types/analytics/posthog.d.ts +45 -0
  119. package/dist/types/analytics/schema.d.ts +320 -0
  120. package/dist/types/analytics/script.d.ts +46 -0
  121. package/dist/types/analytics/segment.d.ts +50 -0
  122. package/dist/types/analytics/vercel.d.ts +48 -0
  123. package/dist/types/astro/integration.d.ts +76 -0
  124. package/dist/types/astro/markdown-negotiation.d.ts +23 -0
  125. package/dist/types/astro/module-types.d.ts +14 -0
  126. package/dist/types/astro/pages.d.ts +44 -0
  127. package/dist/types/cli/env.d.ts +12 -0
  128. package/dist/types/cli/init/scaffold.d.ts +154 -0
  129. package/dist/types/components/layout/nav-utils.d.ts +11 -0
  130. package/dist/types/core/adapter.d.ts +47 -0
  131. package/dist/types/core/api-name.d.ts +7 -0
  132. package/dist/types/core/changelog-index.d.ts +13 -0
  133. package/dist/types/core/config-input.d.ts +186 -515
  134. package/dist/types/core/config.d.ts +60 -34
  135. package/dist/types/core/content-assets.d.ts +76 -0
  136. package/dist/types/core/custom-pages.d.ts +33 -0
  137. package/dist/types/core/data.d.ts +27 -12
  138. package/dist/types/core/define-components.d.ts +12 -9
  139. package/dist/types/core/deployment-env.d.ts +6 -11
  140. package/dist/types/core/frontmatter.d.ts +10 -0
  141. package/dist/types/core/graph.d.ts +18 -0
  142. package/dist/types/core/heading-markers.d.ts +54 -0
  143. package/dist/types/core/i18n-ui.d.ts +6 -2
  144. package/dist/types/core/i18n.d.ts +87 -0
  145. package/dist/types/core/includes.d.ts +138 -0
  146. package/dist/types/core/last-modified.d.ts +47 -0
  147. package/dist/types/core/links.d.ts +95 -0
  148. package/dist/types/core/locale-links.d.ts +60 -0
  149. package/dist/types/core/manifest.d.ts +17 -0
  150. package/dist/types/core/meta.d.ts +38 -0
  151. package/dist/types/core/nav-diagnostics.d.ts +26 -0
  152. package/dist/types/core/navigation.d.ts +6 -0
  153. package/dist/types/core/node-require.d.ts +19 -0
  154. package/dist/types/core/package-json.d.ts +13 -0
  155. package/dist/types/core/probe.d.ts +42 -0
  156. package/dist/types/core/project-graph.d.ts +51 -0
  157. package/dist/types/core/project.d.ts +2 -0
  158. package/dist/types/core/safe-href.d.ts +2 -0
  159. package/dist/types/core/safe-links.d.ts +26 -0
  160. package/dist/types/core/schema.d.ts +2282 -452
  161. package/dist/types/core/site-url.d.ts +16 -0
  162. package/dist/types/core/sources/assets.d.ts +36 -0
  163. package/dist/types/core/sources/cache.d.ts +37 -0
  164. package/dist/types/core/sources/collection.d.ts +28 -0
  165. package/dist/types/core/sources/contentful-rich-text.d.ts +31 -0
  166. package/dist/types/core/sources/contentful.d.ts +40 -0
  167. package/dist/types/core/sources/filesystem.d.ts +24 -0
  168. package/dist/types/core/sources/github-releases.d.ts +31 -0
  169. package/dist/types/core/sources/json.d.ts +30 -0
  170. package/dist/types/core/sources/lexical.d.ts +19 -0
  171. package/dist/types/core/sources/lower.d.ts +75 -0
  172. package/dist/types/core/sources/mdx-remote.d.ts +28 -0
  173. package/dist/types/core/sources/normalize.d.ts +147 -0
  174. package/dist/types/core/sources/notion.d.ts +131 -0
  175. package/dist/types/core/sources/obsidian.d.ts +46 -0
  176. package/dist/types/core/sources/payload.d.ts +39 -0
  177. package/dist/types/core/sources/portable-text.d.ts +42 -0
  178. package/dist/types/core/sources/read.d.ts +23 -0
  179. package/dist/types/core/sources/remote.d.ts +74 -0
  180. package/dist/types/core/sources/resolve.d.ts +19 -0
  181. package/dist/types/core/sources/sanity.d.ts +43 -0
  182. package/dist/types/core/sources/strapi-blocks.d.ts +12 -0
  183. package/dist/types/core/sources/strapi.d.ts +33 -0
  184. package/dist/types/core/sources/types.d.ts +7 -0
  185. package/dist/types/core/sources/watch.d.ts +45 -0
  186. package/dist/types/core/text-width.d.ts +11 -0
  187. package/dist/types/core/types.d.ts +15 -1
  188. package/dist/types/core/unrecognized-keys.d.ts +7 -0
  189. package/dist/types/core/versions.d.ts +72 -0
  190. package/dist/types/core/yaml.d.ts +9 -0
  191. package/dist/types/deploy/adapter-output.d.ts +45 -0
  192. package/dist/types/deploy/adapters/cloudflare.d.ts +40 -0
  193. package/dist/types/deploy/adapters/index.d.ts +29 -0
  194. package/dist/types/deploy/adapters/netlify.d.ts +37 -0
  195. package/dist/types/deploy/adapters/node.d.ts +37 -0
  196. package/dist/types/deploy/adapters/registry.d.ts +133 -0
  197. package/dist/types/deploy/adapters/types.d.ts +71 -0
  198. package/dist/types/deploy/adapters/vercel.d.ts +38 -0
  199. package/dist/types/deploy/artifacts.d.ts +65 -0
  200. package/dist/types/deploy/cloudflare-negotiation.d.ts +196 -0
  201. package/dist/types/deploy/function-bundle.d.ts +80 -0
  202. package/dist/types/deploy/headers.d.ts +50 -0
  203. package/dist/types/deploy/node-headers.d.ts +42 -0
  204. package/dist/types/deploy/platforms/cloudflare.d.ts +40 -0
  205. package/dist/types/deploy/platforms/index.d.ts +18 -0
  206. package/dist/types/deploy/platforms/netlify.d.ts +13 -0
  207. package/dist/types/deploy/platforms/node.d.ts +11 -0
  208. package/dist/types/deploy/platforms/paths.d.ts +27 -0
  209. package/dist/types/deploy/platforms/static.d.ts +10 -0
  210. package/dist/types/deploy/platforms/types.d.ts +104 -0
  211. package/dist/types/deploy/platforms/vercel.d.ts +32 -0
  212. package/dist/types/deploy/redirects.d.ts +53 -0
  213. package/dist/types/deploy/robots.d.ts +8 -0
  214. package/dist/types/deploy/rss.d.ts +31 -0
  215. package/dist/types/deploy/sitemap.d.ts +21 -0
  216. package/dist/types/deploy/vercel-negotiation.d.ts +109 -0
  217. package/dist/types/markdown/code-title.d.ts +32 -0
  218. package/dist/types/markdown/fence-meta.d.ts +23 -0
  219. package/dist/types/markdown/themes.d.ts +3 -3
  220. package/dist/types/openapi/asyncapi.d.ts +129 -0
  221. package/dist/types/openapi/graphql-build.d.ts +8 -0
  222. package/dist/types/openapi/graphql.d.ts +122 -0
  223. package/dist/types/openapi/model.d.ts +158 -0
  224. package/dist/types/openapi/parse.d.ts +57 -0
  225. package/dist/types/openapi/references.d.ts +37 -25
  226. package/dist/types/openapi/render-mdx.d.ts +33 -0
  227. package/dist/types/openapi/sentence.d.ts +7 -0
  228. package/dist/types/openapi/signature.d.ts +10 -0
  229. package/dist/types/openapi/source.d.ts +22 -0
  230. package/dist/types/openapi/spec-dependency-error.d.ts +10 -0
  231. package/dist/types/reference/asyncapi.d.ts +166 -0
  232. package/dist/types/reference/graphql.d.ts +181 -0
  233. package/dist/types/reference/index.d.ts +32 -0
  234. package/dist/types/reference/openapi.d.ts +165 -0
  235. package/dist/types/reference/options.d.ts +157 -0
  236. package/dist/types/reference/scalar.d.ts +136 -0
  237. package/dist/types/reference/schema.d.ts +630 -0
  238. package/dist/types/search/adapters/algolia.d.ts +32 -0
  239. package/dist/types/search/adapters/flexsearch.d.ts +12 -0
  240. package/dist/types/search/adapters/index.d.ts +31 -0
  241. package/dist/types/search/adapters/mixedbread.d.ts +22 -0
  242. package/dist/types/search/adapters/orama-cloud.d.ts +33 -0
  243. package/dist/types/search/adapters/orama.d.ts +13 -0
  244. package/dist/types/search/adapters/pagefind.d.ts +12 -0
  245. package/dist/types/search/adapters/registry.d.ts +228 -0
  246. package/dist/types/search/adapters/types.d.ts +34 -0
  247. package/dist/types/search/adapters/typesense.d.ts +42 -0
  248. package/dist/types/search/build.d.ts +23 -0
  249. package/dist/types/search/documents.d.ts +90 -0
  250. package/dist/types/search/facets.d.ts +3 -0
  251. package/dist/types/search/sync/algolia.d.ts +14 -0
  252. package/dist/types/search/sync/index.d.ts +14 -0
  253. package/dist/types/search/sync/orama-cloud.d.ts +10 -0
  254. package/dist/types/search/sync/typesense.d.ts +14 -0
  255. package/dist/types/sources/contentful.d.ts +68 -0
  256. package/dist/types/sources/custom.d.ts +20 -0
  257. package/dist/types/sources/filesystem.d.ts +42 -0
  258. package/dist/types/sources/github-releases.d.ts +46 -0
  259. package/dist/types/sources/index.d.ts +48 -0
  260. package/dist/types/sources/mdx-remote.d.ts +63 -0
  261. package/dist/types/sources/notion.d.ts +65 -0
  262. package/dist/types/sources/obsidian.d.ts +34 -0
  263. package/dist/types/sources/payload.d.ts +68 -0
  264. package/dist/types/sources/registry.d.ts +1414 -0
  265. package/dist/types/sources/sanity.d.ts +69 -0
  266. package/dist/types/sources/shared.d.ts +46 -0
  267. package/dist/types/sources/strapi.d.ts +66 -0
  268. package/dist/types/theme/icon-kind.d.ts +11 -0
  269. package/dist/types/theme/icons.d.ts +20 -0
  270. package/docs/01-quickstart.mdx +18 -18
  271. package/docs/02-deployment.mdx +50 -26
  272. package/docs/03-upgrading.mdx +351 -0
  273. package/docs/04-migrating.mdx +58 -0
  274. package/docs/08-faq.mdx +3 -3
  275. package/docs/advanced/blog.mdx +13 -6
  276. package/docs/advanced/changelog.mdx +23 -35
  277. package/docs/advanced/custom-pages.mdx +20 -8
  278. package/docs/advanced/meta.ts +1 -8
  279. package/docs/advanced/skills.mdx +8 -0
  280. package/docs/cli/audit.mdx +646 -0
  281. package/docs/cli/doctor.mdx +29 -0
  282. package/docs/{reference/eval.mdx → cli/evals.mdx} +9 -8
  283. package/docs/cli/index.mdx +105 -0
  284. package/docs/cli/meta.ts +7 -0
  285. package/docs/{reference → cli}/translate.mdx +1 -1
  286. package/docs/cli/validate.mdx +41 -0
  287. package/docs/cli/version.mdx +40 -0
  288. package/docs/configuration/analytics.mdx +350 -59
  289. package/docs/configuration/ask-ai.mdx +162 -61
  290. package/docs/configuration/customization.mdx +15 -9
  291. package/docs/configuration/index.mdx +50 -31
  292. package/docs/configuration/search.mdx +75 -54
  293. package/docs/configuration/theming.mdx +24 -15
  294. package/docs/content/components.mdx +21 -5
  295. package/docs/{reference → content}/frontmatter.mdx +35 -1
  296. package/docs/content/i18n.mdx +8 -6
  297. package/docs/content/includes.mdx +2 -4
  298. package/docs/content/index.mdx +1 -1
  299. package/docs/content/islands.mdx +10 -5
  300. package/docs/content/meta.mdx +1 -1
  301. package/docs/content/meta.ts +1 -0
  302. package/docs/content/navigation.mdx +9 -7
  303. package/docs/content/sources.mdx +145 -50
  304. package/docs/content/syntax.mdx +16 -12
  305. package/docs/content/versioning.mdx +3 -3
  306. package/docs/discoverability/agent-discovery.mdx +11 -11
  307. package/docs/discoverability/index.mdx +4 -4
  308. package/docs/discoverability/json-api.mdx +4 -4
  309. package/docs/discoverability/llms-txt.mdx +6 -6
  310. package/docs/discoverability/markdown.mdx +4 -4
  311. package/docs/discoverability/mcp.mdx +11 -11
  312. package/docs/discoverability/sitemap-and-robots.mdx +3 -3
  313. package/docs/index.mdx +2 -2
  314. package/docs/references/asyncapi.mdx +59 -0
  315. package/docs/{advanced → references}/graphql.mdx +45 -32
  316. package/docs/{reference → references}/meta.ts +2 -2
  317. package/docs/references/openapi.mdx +171 -0
  318. package/docs/references/scalar.mdx +64 -0
  319. package/package.json +42 -8
  320. package/skills/blume/SKILL.md +21 -8
  321. package/skills/blume-migrate/SKILL.md +22 -21
  322. package/skills/blume-migrate/assets/oxfmt@0.67.0.patch +49 -0
  323. package/skills/blume-migrate/references/docusaurus.md +5 -4
  324. package/skills/blume-migrate/references/fumadocs.md +4 -4
  325. package/skills/blume-migrate/references/mintlify.md +23 -8
  326. package/skills/blume-migrate/references/monorepo.md +6 -6
  327. package/skills/blume-migrate/references/starlight.md +4 -4
  328. package/src/ai/agent-readability.ts +21 -22
  329. package/src/ai/ai-catalog.ts +50 -32
  330. package/src/ai/api/handlers.ts +46 -11
  331. package/src/ai/api-catalog.ts +8 -8
  332. package/src/ai/ask-data.ts +1 -1
  333. package/src/ai/ask.ts +624 -100
  334. package/src/ai/changelog-markdown.ts +91 -0
  335. package/src/ai/component-markdown.ts +328 -12
  336. package/src/ai/index.ts +45 -0
  337. package/src/ai/link-headers.ts +7 -6
  338. package/src/ai/llms.ts +20 -15
  339. package/src/ai/markdown.ts +35 -5
  340. package/src/ai/mcp/data.ts +9 -5
  341. package/src/ai/openapi-components.ts +4 -1
  342. package/src/ai/relative-links.ts +170 -0
  343. package/src/ai/serializers.ts +2 -2
  344. package/src/ai/skills.ts +1 -1
  345. package/src/ai/web-bot-auth.ts +2 -2
  346. package/src/analytics/adobe.ts +46 -0
  347. package/src/analytics/amplitude.ts +79 -0
  348. package/src/analytics/clarity.ts +46 -0
  349. package/src/analytics/clearbit.ts +47 -0
  350. package/src/analytics/cloudflare.ts +67 -0
  351. package/src/analytics/fathom.ts +64 -0
  352. package/src/analytics/google-analytics.ts +84 -0
  353. package/src/analytics/google-tag-manager.ts +61 -0
  354. package/src/analytics/head.ts +130 -0
  355. package/src/analytics/heap.ts +65 -0
  356. package/src/analytics/hightouch.ts +77 -0
  357. package/src/analytics/hotjar.ts +47 -0
  358. package/src/analytics/index.ts +71 -0
  359. package/src/analytics/inline.ts +21 -0
  360. package/src/analytics/logrocket.ts +71 -0
  361. package/src/analytics/mixpanel.ts +92 -0
  362. package/src/analytics/pirsch.ts +66 -0
  363. package/src/analytics/plausible.ts +83 -0
  364. package/src/analytics/posthog.ts +78 -0
  365. package/src/analytics/schema.ts +60 -0
  366. package/src/analytics/script.ts +60 -0
  367. package/src/analytics/segment.ts +85 -0
  368. package/src/analytics/vercel.ts +50 -0
  369. package/src/astro/adapter-root.ts +7 -9
  370. package/src/astro/component-slots.ts +131 -91
  371. package/src/astro/generate.ts +112 -345
  372. package/src/astro/integration.ts +2 -6
  373. package/src/astro/pages.ts +13 -101
  374. package/src/astro/render-deps.ts +379 -0
  375. package/src/astro/runtime-deps.ts +201 -0
  376. package/src/astro/templates.ts +554 -541
  377. package/src/audit/agent.ts +24 -0
  378. package/src/audit/catalog.ts +2 -2
  379. package/src/audit/checks/assets.ts +2 -2
  380. package/src/audit/checks/dns-aid.ts +1 -1
  381. package/src/audit/checks/duplicates.ts +3 -1
  382. package/src/audit/checks/i18n.ts +1 -1
  383. package/src/audit/checks/indexability.ts +7 -7
  384. package/src/audit/checks/links.ts +2 -2
  385. package/src/audit/checks/llms.ts +6 -6
  386. package/src/audit/checks/network.ts +3 -3
  387. package/src/audit/checks/og-image.ts +2 -2
  388. package/src/audit/checks/robots.ts +1 -1
  389. package/src/audit/checks/sitemap.ts +2 -2
  390. package/src/audit/checks/social.ts +1 -1
  391. package/src/audit/run.ts +4 -3
  392. package/src/audit/terms.ts +31 -0
  393. package/src/audit/url.ts +13 -13
  394. package/src/cli/command-meta.ts +10 -0
  395. package/src/cli/commands/audit.ts +39 -20
  396. package/src/cli/commands/build.ts +71 -316
  397. package/src/cli/commands/check.ts +1 -0
  398. package/src/cli/commands/dev.ts +40 -1
  399. package/src/cli/commands/doctor.ts +90 -14
  400. package/src/cli/commands/eject.ts +44 -7
  401. package/src/cli/commands/eval.ts +2 -2
  402. package/src/cli/commands/init.ts +161 -41
  403. package/src/cli/commands/migrate.ts +121 -0
  404. package/src/cli/commands/preview.ts +7 -1
  405. package/src/cli/commands/translate.ts +2 -2
  406. package/src/cli/commands/upgrade.ts +141 -0
  407. package/src/cli/commands/version.ts +57 -44
  408. package/src/cli/eject-scripts.ts +121 -6
  409. package/src/cli/index.ts +11 -1
  410. package/src/cli/init/install.ts +70 -0
  411. package/src/cli/init/questions.ts +7 -0
  412. package/src/cli/init/scaffold.ts +459 -75
  413. package/src/cli/lazy-command.ts +37 -1
  414. package/src/cli/prepare.ts +40 -6
  415. package/src/cli/required-secrets.ts +38 -11
  416. package/src/cli/unknown-flags.ts +266 -0
  417. package/src/cli/yarn-pnp.ts +52 -0
  418. package/src/components/content/AccordionItem.astro +7 -1
  419. package/src/components/content/Card.astro +4 -3
  420. package/src/components/content/ColorItem.astro +22 -4
  421. package/src/components/content/GithubInfo.astro +2 -2
  422. package/src/components/content/Prompt.astro +25 -25
  423. package/src/components/content/Tabs.astro +3 -1
  424. package/src/components/content/Tile.astro +2 -3
  425. package/src/components/content/Tooltip.astro +57 -9
  426. package/src/components/content/content-strings.ts +34 -0
  427. package/src/components/content/diff.ts +1 -1
  428. package/src/components/content/mermaid-element.ts +13 -2
  429. package/src/components/content/prompt-markdown.ts +292 -0
  430. package/src/components/content/tooltip-id.ts +41 -0
  431. package/src/components/islands/hooks.ts +25 -2
  432. package/src/components/layout/Analytics.astro +21 -79
  433. package/src/components/layout/Banner.astro +3 -1
  434. package/src/components/layout/DiscoveryLinks.astro +69 -0
  435. package/src/components/layout/Header.astro +83 -20
  436. package/src/components/layout/LanguageSwitcher.astro +4 -1
  437. package/src/components/layout/Logo.astro +23 -2
  438. package/src/components/layout/NavSelector.astro +13 -2
  439. package/src/components/layout/NavTree.astro +12 -3
  440. package/src/components/layout/NavTreeScript.astro +17 -3
  441. package/src/components/layout/PageActions.astro +3 -3
  442. package/src/components/layout/PageFeedback.astro +11 -1
  443. package/src/components/layout/PageLayout.astro +52 -5
  444. package/src/components/layout/ReferenceLayout.astro +21 -12
  445. package/src/components/layout/RootLayout.astro +69 -66
  446. package/src/components/layout/Search.astro +30 -6
  447. package/src/components/layout/WebMcp.astro +1 -1
  448. package/src/components/layout/analytics-client.ts +72 -15
  449. package/src/components/layout/drawer-inert.ts +113 -15
  450. package/src/components/layout/dropdown-clamp.ts +105 -0
  451. package/src/components/layout/head-scripts.ts +15 -6
  452. package/src/components/layout/nav-utils.ts +17 -0
  453. package/src/components/layout/search/algolia.ts +8 -8
  454. package/src/components/layout/search/orama-cloud.ts +9 -7
  455. package/src/components/layout/search/typesense.ts +11 -17
  456. package/src/components/openapi/MessageComposer.astro +2 -2
  457. package/src/components/openapi/Playground.astro +2 -2
  458. package/src/components/openapi/playground-client.ts +79 -10
  459. package/src/core/changelog-index.ts +24 -0
  460. package/src/core/component-overrides.ts +399 -154
  461. package/src/core/config-input.ts +188 -553
  462. package/src/core/config.ts +130 -41
  463. package/src/core/custom-pages.ts +105 -0
  464. package/src/core/data.ts +31 -12
  465. package/src/core/define-components.ts +12 -9
  466. package/src/core/deployment-env.ts +18 -74
  467. package/src/core/diagnostics.ts +14 -7
  468. package/src/core/graph.ts +69 -25
  469. package/src/core/i18n-ui.ts +6 -2
  470. package/src/core/i18n.ts +17 -0
  471. package/src/core/includes.ts +156 -38
  472. package/src/core/last-modified.ts +6 -11
  473. package/src/core/links.ts +166 -2
  474. package/src/core/manifest.ts +10 -3
  475. package/src/core/navigation.ts +115 -16
  476. package/src/core/new-tab.ts +35 -0
  477. package/src/core/node-require.ts +21 -0
  478. package/src/core/project-graph.ts +42 -26
  479. package/src/core/project.ts +9 -4
  480. package/src/core/request-body.ts +61 -0
  481. package/src/core/safe-href.ts +28 -0
  482. package/src/core/safe-links.ts +68 -0
  483. package/src/core/schema.ts +517 -739
  484. package/src/core/server-features.ts +8 -9
  485. package/src/core/sources/assets.ts +47 -11
  486. package/src/core/sources/collection.ts +67 -0
  487. package/src/core/sources/contentful-rich-text.ts +285 -0
  488. package/src/core/sources/contentful.ts +173 -0
  489. package/src/core/sources/github-releases.ts +51 -1
  490. package/src/core/sources/json.ts +71 -0
  491. package/src/core/sources/lexical.ts +195 -0
  492. package/src/core/sources/lower.ts +226 -0
  493. package/src/core/sources/normalize.ts +52 -2
  494. package/src/core/sources/notion.ts +39 -28
  495. package/src/core/sources/payload.ts +135 -0
  496. package/src/core/sources/portable-text.ts +11 -16
  497. package/src/core/sources/remote.ts +226 -0
  498. package/src/core/sources/resolve.ts +104 -166
  499. package/src/core/sources/sanity.ts +16 -49
  500. package/src/core/sources/strapi-blocks.ts +124 -0
  501. package/src/core/sources/strapi.ts +191 -0
  502. package/src/core/sources/types.ts +12 -1
  503. package/src/core/types.ts +15 -1
  504. package/src/core/ui-packs/ar.ts +6 -2
  505. package/src/core/ui-packs/bg.ts +6 -2
  506. package/src/core/ui-packs/bn.ts +6 -2
  507. package/src/core/ui-packs/ca.ts +3 -1
  508. package/src/core/ui-packs/cs.ts +6 -2
  509. package/src/core/ui-packs/da.ts +6 -2
  510. package/src/core/ui-packs/de.ts +6 -2
  511. package/src/core/ui-packs/el.ts +3 -1
  512. package/src/core/ui-packs/es.ts +3 -1
  513. package/src/core/ui-packs/fa.ts +6 -2
  514. package/src/core/ui-packs/fi.ts +6 -2
  515. package/src/core/ui-packs/fr.ts +3 -1
  516. package/src/core/ui-packs/he.ts +6 -2
  517. package/src/core/ui-packs/hi.ts +6 -2
  518. package/src/core/ui-packs/hr.ts +6 -2
  519. package/src/core/ui-packs/hu.ts +6 -2
  520. package/src/core/ui-packs/id.ts +6 -2
  521. package/src/core/ui-packs/it.ts +3 -1
  522. package/src/core/ui-packs/ja.ts +3 -1
  523. package/src/core/ui-packs/ko.ts +3 -1
  524. package/src/core/ui-packs/nl.ts +6 -2
  525. package/src/core/ui-packs/no.ts +6 -2
  526. package/src/core/ui-packs/pl.ts +6 -2
  527. package/src/core/ui-packs/pt-br.ts +3 -1
  528. package/src/core/ui-packs/pt.ts +3 -1
  529. package/src/core/ui-packs/ro.ts +6 -2
  530. package/src/core/ui-packs/ru.ts +6 -2
  531. package/src/core/ui-packs/sk.ts +6 -2
  532. package/src/core/ui-packs/sr.ts +6 -2
  533. package/src/core/ui-packs/sv.ts +6 -2
  534. package/src/core/ui-packs/th.ts +3 -1
  535. package/src/core/ui-packs/tr.ts +6 -2
  536. package/src/core/ui-packs/uk.ts +6 -2
  537. package/src/core/ui-packs/vi.ts +3 -1
  538. package/src/core/ui-packs/zh-tw.ts +3 -1
  539. package/src/core/ui-packs/zh.ts +3 -1
  540. package/src/core/unrecognized-keys.ts +10 -0
  541. package/src/core/version-cut.ts +116 -8
  542. package/src/deploy/adapter-output.ts +57 -97
  543. package/src/deploy/adapters/cloudflare.ts +39 -0
  544. package/src/deploy/adapters/index.ts +41 -0
  545. package/src/deploy/adapters/netlify.ts +34 -0
  546. package/src/deploy/adapters/node.ts +34 -0
  547. package/src/deploy/adapters/registry.ts +127 -0
  548. package/src/deploy/adapters/types.ts +92 -0
  549. package/src/deploy/adapters/vercel.ts +35 -0
  550. package/src/deploy/artifacts.ts +55 -27
  551. package/src/deploy/cloudflare-negotiation.ts +179 -100
  552. package/src/deploy/function-bundle.ts +18 -3
  553. package/src/deploy/headers.ts +124 -23
  554. package/src/deploy/node-headers.ts +198 -0
  555. package/src/deploy/platforms/cloudflare.ts +312 -0
  556. package/src/deploy/platforms/index.ts +67 -0
  557. package/src/deploy/platforms/netlify.ts +52 -0
  558. package/src/deploy/platforms/node.ts +40 -0
  559. package/src/deploy/platforms/paths.ts +42 -0
  560. package/src/deploy/platforms/static.ts +29 -0
  561. package/src/deploy/platforms/types.ts +112 -0
  562. package/src/deploy/platforms/vercel.ts +193 -0
  563. package/src/deploy/redirects.ts +30 -18
  564. package/src/deploy/robots.ts +3 -3
  565. package/src/deploy/rss.ts +2 -2
  566. package/src/deploy/sitemap.ts +2 -2
  567. package/src/deploy/vercel-negotiation.ts +2 -2
  568. package/src/eval/agents.ts +32 -1
  569. package/src/eval/findings.ts +19 -11
  570. package/src/eval/report.ts +10 -2
  571. package/src/markdown/external-links.ts +65 -0
  572. package/src/markdown/include.ts +45 -27
  573. package/src/markdown/index.ts +34 -9
  574. package/src/markdown/inline-code.ts +12 -6
  575. package/src/markdown/relative-links.ts +324 -0
  576. package/src/markdown/themes.ts +3 -3
  577. package/src/migrate/migrate.ts +149 -0
  578. package/src/openapi/parse.ts +64 -16
  579. package/src/openapi/proxy.ts +62 -10
  580. package/src/openapi/references.ts +147 -147
  581. package/src/openapi/render-mdx.ts +32 -10
  582. package/src/openapi/scalar.ts +15 -13
  583. package/src/openapi/sentence.ts +14 -0
  584. package/src/openapi/source.ts +37 -21
  585. package/src/openapi/spec-dependency-error.ts +15 -0
  586. package/src/reference/asyncapi.ts +83 -0
  587. package/src/reference/graphql.ts +89 -0
  588. package/src/reference/index.ts +40 -0
  589. package/src/reference/openapi.ts +83 -0
  590. package/src/reference/options.ts +201 -0
  591. package/src/reference/scalar.ts +136 -0
  592. package/src/reference/schema.ts +88 -0
  593. package/src/registry/eject.ts +97 -42
  594. package/src/search/adapters/algolia.ts +46 -0
  595. package/src/search/adapters/flexsearch.ts +29 -0
  596. package/src/search/adapters/index.ts +39 -0
  597. package/src/search/adapters/mixedbread.ts +39 -0
  598. package/src/search/adapters/orama-cloud.ts +51 -0
  599. package/src/search/adapters/orama.ts +24 -0
  600. package/src/search/adapters/pagefind.ts +27 -0
  601. package/src/search/adapters/registry.ts +131 -0
  602. package/src/search/adapters/types.ts +46 -0
  603. package/src/search/adapters/typesense.ts +57 -0
  604. package/src/search/build.ts +8 -4
  605. package/src/search/documents.ts +6 -0
  606. package/src/search/sync/algolia.ts +12 -11
  607. package/src/search/sync/index.ts +35 -20
  608. package/src/search/sync/orama-cloud.ts +12 -9
  609. package/src/search/sync/typesense.ts +15 -13
  610. package/src/sources/contentful.ts +63 -0
  611. package/src/sources/custom.ts +38 -0
  612. package/src/sources/filesystem.ts +51 -0
  613. package/src/sources/github-releases.ts +52 -0
  614. package/src/sources/index.ts +60 -0
  615. package/src/sources/mdx-remote.ts +76 -0
  616. package/src/sources/notion.ts +63 -0
  617. package/src/sources/obsidian.ts +38 -0
  618. package/src/sources/payload.ts +60 -0
  619. package/src/sources/registry.ts +182 -0
  620. package/src/sources/sanity.ts +66 -0
  621. package/src/sources/shared.ts +52 -0
  622. package/src/sources/strapi.ts +58 -0
  623. package/src/theme/entry.ts +24 -2
  624. package/src/translate/report.ts +40 -7
  625. package/src/upgrade/upgrade.ts +499 -0
  626. package/dist/cli/chunk-0qymqwzz.js +0 -164
  627. package/dist/cli/chunk-0qymqwzz.js.map +0 -15
  628. package/dist/cli/chunk-1jefwnfs.js.map +0 -48
  629. package/dist/cli/chunk-2mzebbbz.js +0 -69
  630. package/dist/cli/chunk-2z47ypj8.js.map +0 -11
  631. package/dist/cli/chunk-4x36ddpw.js.map +0 -11
  632. package/dist/cli/chunk-5093q3n7.js +0 -68
  633. package/dist/cli/chunk-5093q3n7.js.map +0 -10
  634. package/dist/cli/chunk-5qk08vmp.js.map +0 -11
  635. package/dist/cli/chunk-7s8hm3b6.js +0 -5347
  636. package/dist/cli/chunk-7s8hm3b6.js.map +0 -58
  637. package/dist/cli/chunk-8cjtbafj.js.map +0 -13
  638. package/dist/cli/chunk-97r59kpr.js +0 -381
  639. package/dist/cli/chunk-97r59kpr.js.map +0 -12
  640. package/dist/cli/chunk-ahnw3kxw.js.map +0 -15
  641. package/dist/cli/chunk-b27xqwn9.js.map +0 -10
  642. package/dist/cli/chunk-bf6bt1xt.js +0 -185
  643. package/dist/cli/chunk-bf6bt1xt.js.map +0 -11
  644. package/dist/cli/chunk-cjtn640a.js.map +0 -10
  645. package/dist/cli/chunk-ct47dqpx.js.map +0 -11
  646. package/dist/cli/chunk-esphfr8p.js +0 -107
  647. package/dist/cli/chunk-esphfr8p.js.map +0 -11
  648. package/dist/cli/chunk-ex56aa81.js +0 -1016
  649. package/dist/cli/chunk-ex56aa81.js.map +0 -13
  650. package/dist/cli/chunk-garjf5z9.js +0 -30
  651. package/dist/cli/chunk-garjf5z9.js.map +0 -10
  652. package/dist/cli/chunk-js7saxwm.js +0 -1045
  653. package/dist/cli/chunk-js7saxwm.js.map +0 -22
  654. package/dist/cli/chunk-k79xp7av.js.map +0 -39
  655. package/dist/cli/chunk-ps4m1xh4.js.map +0 -15
  656. package/dist/cli/chunk-q4rae3bg.js +0 -60
  657. package/dist/cli/chunk-q4rae3bg.js.map +0 -10
  658. package/dist/cli/chunk-rz9jmfhz.js +0 -108
  659. package/dist/cli/chunk-rz9jmfhz.js.map +0 -10
  660. package/dist/cli/chunk-vh9w1sgp.js +0 -73
  661. package/dist/cli/chunk-vh9w1sgp.js.map +0 -10
  662. package/dist/cli/chunk-vrfp10qk.js +0 -81
  663. package/dist/cli/chunk-vrfp10qk.js.map +0 -10
  664. package/dist/cli/chunk-yg63d42r.js.map +0 -34
  665. package/docs/advanced/api-reference.mdx +0 -240
  666. package/docs/reference/cli.mdx +0 -197
  667. package/skills/blume-migrate/assets/oxfmt@0.55.0.patch +0 -20
  668. package/src/components/content/changelog-element.ts +0 -69
  669. package/src/search/providers.ts +0 -91
  670. /package/dist/cli/{chunk-e7f42gdj.js.map → chunk-7ez8ny0t.js.map} +0 -0
  671. /package/dist/cli/{chunk-nn13znc2.js.map → chunk-88cpgt6h.js.map} +0 -0
  672. /package/dist/cli/{chunk-vacwm2hv.js.map → chunk-f7t03s3g.js.map} +0 -0
  673. /package/dist/cli/{chunk-dwgcp5sm.js.map → chunk-zxccj738.js.map} +0 -0
@@ -161,6 +161,28 @@ export interface UseAskAIOptions {
161
161
  /** Shown as the assistant's answer when the request fails or throws. */
162
162
  const ASK_ERROR = "Something went wrong answering that. Please try again.";
163
163
 
164
+ /** How the generated Ask AI route opens its missing-credential notice. */
165
+ const NOT_CONFIGURED = /^Ask AI is not configured/u;
166
+
167
+ /**
168
+ * The route's own explanation of a 503 — the generated route's "Ask AI is not
169
+ * configured: set AI_GATEWAY_API_KEY." — when the response is that notice, or
170
+ * null. It names only the variable to set, never a value, so it's safe to
171
+ * show, and it tells a site owner trying a fresh deploy what's missing instead
172
+ * of the generic failure; any other error body (an HTML error page, a proxy's
173
+ * message) still gets the generic notice.
174
+ */
175
+ const unavailableNotice = async (
176
+ response: Response
177
+ ): Promise<string | null> => {
178
+ if (response.status !== 503) {
179
+ return null;
180
+ }
181
+ const body = await response.text();
182
+ const text = body.trim();
183
+ return NOT_CONFIGURED.test(text) && text.length <= 200 ? text : null;
184
+ };
185
+
164
186
  /** The current route with the deployment base stripped, for page grounding. */
165
187
  const currentPath = (): string =>
166
188
  stripBase(import.meta.env.BASE_URL, window.location.pathname);
@@ -253,10 +275,11 @@ export const useAskAI = (options: UseAskAIOptions = {}): UseAskAI => {
253
275
  ({ status } = response);
254
276
  if (!response.ok) {
255
277
  // An error body (JSON, HTML error page) must not stream in as the
256
- // assistant's answer.
278
+ // assistant's answer — only the route's own not-configured notice.
279
+ const notice = await unavailableNotice(response).catch(() => null);
257
280
  if (live()) {
258
281
  outcome("ask_error", { status });
259
- assistant.content = errorMessage;
282
+ assistant.content = notice ?? errorMessage;
260
283
  setMessages([...history, { ...assistant }]);
261
284
  }
262
285
  return;
@@ -1,90 +1,32 @@
1
1
  ---
2
2
  import VercelAnalytics from "@vercel/analytics/astro";
3
3
 
4
- // Analytics scripts injected into <head>. PostHog and custom providers use the
5
- // inline-snippet house style (banner/theme/JSON-LD); Vercel uses its official
6
- // Astro component; Cloudflare is its beacon script tag. Loads ONLY in production builds so `blume dev` stays clean
7
- // and local traffic never reaches your analytics.
8
- interface AnalyticsScript {
9
- attributes?: Record<string, string>;
10
- content?: string;
11
- src?: string;
12
- strategy?: "async" | "defer";
13
- }
14
-
4
+ import { analyticsHead } from "../../analytics/head.ts";
5
+ import type { BlumeDataConfig } from "../../core/data.ts";
6
+
7
+ // The analytics adapters' <head> output. Each adapter in `analytics` owns the
8
+ // snippet it emits (src/analytics/*); `analyticsHead` collects them into one
9
+ // list in declared order and this component renders it: the official Vercel
10
+ // component for `vercel()`, one <script> for every other adapter. Loads ONLY
11
+ // in production builds so `blume dev` stays clean and local traffic never
12
+ // reaches your analytics.
15
13
  interface Props {
16
- analytics?: {
17
- cloudflare?: { token: string };
18
- posthog?: { host?: string; key: string };
19
- scripts?: AnalyticsScript[];
20
- vercel?: boolean;
21
- } | null;
14
+ analytics?: BlumeDataConfig["analytics"] | null;
22
15
  }
23
16
 
24
17
  const { analytics } = Astro.props;
25
- const enabled = import.meta.env.PROD && Boolean(analytics);
26
-
27
- // Vercel Web Analytics: rendered via the official @vercel/analytics/astro
28
- // component, which injects the first-party script Vercel serves at
29
- // /_vercel/insights once Web Analytics is enabled for the project.
30
- const vercelEnabled = enabled && analytics?.vercel === true;
31
-
32
- // PostHog: the official array.js loader snippet, keyed off config. The host
33
- // defaults to PostHog Cloud US; EU/self-hosted users override `posthog.host`.
34
- const POSTHOG_LOADER =
35
- '!function(t,e){var o,n,p,r;e.__SV||(window.posthog=e,e._i=[],e.init=function(i,s,a){function g(t,e){var o=e.split(".");2==o.length&&(t=t[o[0]],e=o[1]),t[e]=function(){t.push([e].concat(Array.prototype.slice.call(arguments,0)))}}(p=t.createElement("script")).type="text/javascript",p.crossOrigin="anonymous",p.async=!0,p.src=s.api_host.replace(".i.posthog.com","-assets.i.posthog.com")+"/static/array.js",(r=t.getElementsByTagName("script")[0]).parentNode.insertBefore(p,r);var u=e;for(void 0!==a?u=e[a]=[]:a="posthog",u.people=u.people||[],u.toString=function(t){var e="posthog";return"posthog"!==a&&(e+="."+a),t||(e+=" (stub)"),e},u.people.toString=function(){return u.toString(1)+".people (stub)"},o="init capture register register_once register_for_session unregister unregister_for_session getFeatureFlag getFeatureFlagPayload isFeatureEnabled reloadFeatureFlags updateEarlyAccessFeatureEnrollment getEarlyAccessFeatures on onFeatureFlags onSessionId getSurveys getActiveMatchingSurveys renderSurvey canRenderSurvey getNextSurveyStep identify setPersonProperties group resetGroups setPersonPropertiesForFlags resetPersonPropertiesForFlags setGroupPropertiesForFlags resetGroupPropertiesForFlags reset get_distinct_id getGroups get_session_id get_session_replay_url alias set_config startSessionRecording stopSessionRecording sessionRecordingStarted captureException loadToolbar get_property getSessionProperty createPersonProfile opt_in_capturing opt_out_capturing has_opted_in_capturing has_opted_out_capturing clear_opt_in_out_capturing debug getPageViewId captureTraceFeedback captureTraceMetric".split(" "),n=0;n<o.length;n++)g(u,o[n]);e._i.push([i,s,a])},e.__SV=1)}(document,window.posthog||[]);';
36
- const posthog = enabled ? analytics?.posthog : undefined;
37
- // PostHog's loader only captures a pageview per real page load, but the client
38
- // router turns link clicks into in-place swaps — capture those too, keyed off
39
- // `astro:page-load` with a pathname guard so the initial load (which the
40
- // loader already counted) and same-page hash moves aren't double-counted.
41
- // Vercel Web Analytics and GA4's enhanced measurement track history changes
42
- // on their own.
43
- const POSTHOG_SPA_PAGEVIEWS =
44
- 'document.addEventListener("astro:page-load",function(){var p=window.__blumePhPath;window.__blumePhPath=location.pathname;if(p!==undefined&&p!==location.pathname){posthog.capture("$pageview");}});';
45
- const posthogSnippet = posthog
46
- ? `${POSTHOG_LOADER}posthog.init(${JSON.stringify(posthog.key)},{api_host:${JSON.stringify(posthog.host ?? "https://us.i.posthog.com")}});${POSTHOG_SPA_PAGEVIEWS}`
47
- : null;
48
-
49
- // Cloudflare Web Analytics (manual setup): the beacon script keyed by the
50
- // site token, exactly as the dashboard's snippet renders it. It tracks
51
- // history changes itself, so client-router navigations need no extra hook.
52
- const cloudflareBeacon =
53
- enabled && analytics?.cloudflare
54
- ? JSON.stringify({ token: analytics.cloudflare.token })
55
- : null;
56
-
57
- // Custom scripts: any other provider. Each is either external (`src`) or inline
58
- // (`content`). Explicit fields win over spread `attributes`.
59
- const customScripts = enabled ? (analytics?.scripts ?? []) : [];
60
- const externalScripts = customScripts
61
- .filter((script) => script.src)
62
- .map((script) => ({
63
- ...script.attributes,
64
- ...(script.strategy === "async" ? { async: true } : {}),
65
- ...(script.strategy === "defer" ? { defer: true } : {}),
66
- src: script.src,
67
- }));
68
- const inlineScripts = customScripts
69
- .filter((script) => script.content)
70
- .map((script) => ({ attributes: script.attributes ?? {}, content: script.content }));
18
+ const nodes = analyticsHead(import.meta.env.PROD ? (analytics ?? []) : []);
71
19
  ---
72
20
 
73
- {vercelEnabled && <VercelAnalytics />}
74
- {
75
- cloudflareBeacon && (
76
- <script
77
- is:inline
78
- defer
79
- src="https://static.cloudflareinsights.com/beacon.min.js"
80
- data-cf-beacon={cloudflareBeacon}
81
- />
82
- )
83
- }
84
- {posthogSnippet && <script is:inline set:html={posthogSnippet} />}
85
- {externalScripts.map((attrs) => <script is:inline {...attrs} />)}
86
21
  {
87
- inlineScripts.map((script) => (
88
- <script is:inline set:html={script.content} {...script.attributes} />
89
- ))
22
+ nodes.map((node) => {
23
+ if (node.type === "vercel") {
24
+ return <VercelAnalytics {...node.props} />;
25
+ }
26
+ return node.content === null ? (
27
+ <script is:inline {...node.attributes} />
28
+ ) : (
29
+ <script is:inline set:html={node.content} {...node.attributes} />
30
+ );
31
+ })
90
32
  }
@@ -34,7 +34,9 @@ const b = { ...EN_UI.banner, ...strings };
34
34
  data-banner-key={banner.key}
35
35
  data-blume-banner
36
36
  >
37
- <span>{banner.content}</span>
37
+ {/* The message is authored in one language, whatever the page's
38
+ direction: `dir="auto"` keeps its punctuation where it was typed. */}
39
+ <span dir="auto">{banner.content}</span>
38
40
  {banner.link && (
39
41
  <a
40
42
  class="inline-flex items-center gap-1 font-medium underline underline-offset-2"
@@ -0,0 +1,69 @@
1
+ ---
2
+ import type { DiscoveryHeadLinks } from "../../core/data.ts";
3
+ import { withBase } from "../islands/base-path.ts";
4
+ // Emits the agent-discovery <link>s for the document <head>, on every page and
5
+ // not just the root: an agent that enters on a deep page (a search result, a
6
+ // shared link) never sees the homepage HTTP `Link` header (see
7
+ // `ai/link-headers.ts`), so the head carries the same `describedby` resources,
8
+ // the AI Catalog / ARD manifest pair, and — when the page has one — its own
9
+ // raw-Markdown mirror as an `alternate`. This also reaches hosts where Blume
10
+ // can't set response headers. Every shell (RootLayout, PageLayout,
11
+ // ReferenceLayout) renders this one partial, so the "every page's head"
12
+ // promise has a single definition. Both `describedby` and `alternate` are
13
+ // IANA-registered relations.
14
+ interface Props {
15
+ /**
16
+ * Which resources exist, from the `blume:data` snapshot; `null` drops every
17
+ * link this partial renders, the Markdown alternate included (a page opting
18
+ * out of the agent-facing surface).
19
+ */
20
+ discovery?: DiscoveryHeadLinks | null;
21
+ /**
22
+ * This page's raw-Markdown mirror (already under the deployment base), or
23
+ * `null` when the route has none — only content routes and the homepage do
24
+ * (see `markdownRoutePaths`). Advertised only while `discovery` is set.
25
+ */
26
+ markdownMirror?: string | null;
27
+ }
28
+
29
+ const { discovery, markdownMirror } = Astro.props;
30
+ ---
31
+
32
+ {
33
+ discovery?.agentReadability && (
34
+ <link
35
+ href={withBase("/agent-readability.json")}
36
+ rel="describedby"
37
+ type="application/json"
38
+ />
39
+ )
40
+ }
41
+ {
42
+ discovery?.llmsTxt && (
43
+ <link href={withBase("/llms.txt")} rel="describedby" type="text/plain" />
44
+ )
45
+ }
46
+ {/* The AI Catalog / ARD manifest under both relations its two specs define:
47
+ `ai-catalog` (ai-catalog spec) and `ard` (ARD v0.91), each pointing at that
48
+ spec's own well-known path. */}
49
+ {
50
+ discovery?.aiCatalog && (
51
+ <>
52
+ <link
53
+ href={withBase("/.well-known/ai-catalog.json")}
54
+ rel="ai-catalog"
55
+ type="application/ai-catalog+json"
56
+ />
57
+ <link
58
+ href={withBase("/.well-known/ard.json")}
59
+ rel="ard"
60
+ type="application/json"
61
+ />
62
+ </>
63
+ )
64
+ }
65
+ {
66
+ discovery && markdownMirror && (
67
+ <link href={markdownMirror} rel="alternate" type="text/markdown" />
68
+ )
69
+ }
@@ -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";
@@ -33,6 +33,11 @@ interface Props {
33
33
  navigation: Navigation;
34
34
  route: string;
35
35
  searchEnabled: boolean;
36
+ /**
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;
36
41
  /**
37
42
  * Whether the search modal offers an "Ask AI" hand-off. Defaults to whether
38
43
  * Ask AI is configured, so no page has to pass it; a layout can still opt a
@@ -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,6 +90,7 @@ const {
79
90
  navigation,
80
91
  route,
81
92
  searchEnabled,
93
+ locale,
82
94
  // `config.ask` is null whenever Ask AI 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.
@@ -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">
@@ -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"
@@ -290,3 +318,38 @@ const clickScript = `(()=>{const dr=()=>{const h=document.querySelector("[data-b
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>