blume 1.7.2 → 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 (677) hide show
  1. package/AGENTS.md +19 -0
  2. package/CHANGELOG.md +227 -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-xhtpx3ff.js → chunk-1w8dp3qb.js} +17 -16
  7. package/dist/cli/{chunk-xhtpx3ff.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-9he6crym.js → chunk-6k8vp3ta.js} +25 -13
  15. package/dist/cli/{chunk-9he6crym.js.map → chunk-6k8vp3ta.js.map} +4 -4
  16. package/dist/cli/{chunk-mt76t7dj.js → chunk-79njf86q.js} +133 -54
  17. package/dist/cli/chunk-79njf86q.js.map +11 -0
  18. package/dist/cli/{chunk-3w7b2vcx.js → chunk-7ez8ny0t.js} +2 -2
  19. package/dist/cli/{chunk-688e0dde.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-hs3gbh8p.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-9bkjd11x.js → chunk-bw22s759.js} +15 -5
  33. package/dist/cli/{chunk-9bkjd11x.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-t3tj0dgr.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-exeeb35e.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-12dxjqk7.js → chunk-fh5hj5jt.js} +44 -21
  48. package/dist/cli/chunk-fh5hj5jt.js.map +10 -0
  49. package/dist/cli/{chunk-8cd8tj54.js → chunk-j8mw0za6.js} +86 -57
  50. package/dist/cli/chunk-j8mw0za6.js.map +35 -0
  51. package/dist/cli/chunk-jts8mvcz.js +106 -0
  52. package/dist/cli/{chunk-2mzebbbz.js.map → chunk-jts8mvcz.js.map} +6 -4
  53. package/dist/cli/{chunk-ejjx8znq.js → chunk-mnqj32sj.js} +505 -536
  54. package/dist/cli/chunk-mnqj32sj.js.map +12 -0
  55. package/dist/cli/{chunk-196vjxp9.js → chunk-mwt1k8n7.js} +100 -372
  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-n9sra6sy.js → chunk-pat2zzwc.js} +10 -14
  60. package/dist/cli/{chunk-n9sra6sy.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-cvky9gb2.js → chunk-sqn5t4q0.js} +81 -87
  64. package/dist/cli/chunk-sqn5t4q0.js.map +10 -0
  65. package/dist/cli/{chunk-eevwt1sc.js → chunk-tzne8qfq.js} +15 -15
  66. package/dist/cli/{chunk-eevwt1sc.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-5n7t497w.js → chunk-z1f5arsg.js} +247 -755
  74. package/dist/cli/chunk-z1f5arsg.js.map +36 -0
  75. package/dist/cli/{chunk-hdm2dkd2.js → chunk-zg2gtj10.js} +1086 -2596
  76. package/dist/cli/chunk-zg2gtj10.js.map +35 -0
  77. package/dist/cli/{chunk-ppfvdcd4.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 +209 -505
  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 +28 -11
  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 +2291 -441
  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 +20 -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 +176 -61
  290. package/docs/configuration/customization.mdx +15 -9
  291. package/docs/configuration/index.mdx +52 -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 +37 -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 +12 -7
  303. package/docs/content/sources.mdx +145 -50
  304. package/docs/content/syntax.mdx +26 -12
  305. package/docs/content/versioning.mdx +3 -3
  306. package/docs/discoverability/agent-discovery.mdx +91 -10
  307. package/docs/discoverability/index.mdx +5 -5
  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 +26 -22
  329. package/src/ai/ai-catalog.ts +259 -0
  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 +19 -6
  338. package/src/ai/llms.ts +26 -15
  339. package/src/ai/markdown.ts +35 -5
  340. package/src/ai/mcp/data.ts +9 -5
  341. package/src/ai/mcp/discovery.ts +1 -1
  342. package/src/ai/openapi-components.ts +4 -1
  343. package/src/ai/relative-links.ts +170 -0
  344. package/src/ai/serializers.ts +2 -2
  345. package/src/ai/skills.ts +1 -1
  346. package/src/ai/web-bot-auth.ts +2 -2
  347. package/src/analytics/adobe.ts +46 -0
  348. package/src/analytics/amplitude.ts +79 -0
  349. package/src/analytics/clarity.ts +46 -0
  350. package/src/analytics/clearbit.ts +47 -0
  351. package/src/analytics/cloudflare.ts +67 -0
  352. package/src/analytics/fathom.ts +64 -0
  353. package/src/analytics/google-analytics.ts +84 -0
  354. package/src/analytics/google-tag-manager.ts +61 -0
  355. package/src/analytics/head.ts +130 -0
  356. package/src/analytics/heap.ts +65 -0
  357. package/src/analytics/hightouch.ts +77 -0
  358. package/src/analytics/hotjar.ts +47 -0
  359. package/src/analytics/index.ts +71 -0
  360. package/src/analytics/inline.ts +21 -0
  361. package/src/analytics/logrocket.ts +71 -0
  362. package/src/analytics/mixpanel.ts +92 -0
  363. package/src/analytics/pirsch.ts +66 -0
  364. package/src/analytics/plausible.ts +83 -0
  365. package/src/analytics/posthog.ts +78 -0
  366. package/src/analytics/schema.ts +60 -0
  367. package/src/analytics/script.ts +60 -0
  368. package/src/analytics/segment.ts +85 -0
  369. package/src/analytics/vercel.ts +50 -0
  370. package/src/astro/adapter-root.ts +7 -9
  371. package/src/astro/component-slots.ts +131 -91
  372. package/src/astro/generate.ts +114 -345
  373. package/src/astro/integration.ts +2 -6
  374. package/src/astro/pages.ts +13 -101
  375. package/src/astro/render-deps.ts +379 -0
  376. package/src/astro/runtime-deps.ts +201 -0
  377. package/src/astro/templates.ts +554 -541
  378. package/src/audit/agent.ts +24 -0
  379. package/src/audit/catalog.ts +2 -2
  380. package/src/audit/checks/assets.ts +2 -2
  381. package/src/audit/checks/dns-aid.ts +1 -1
  382. package/src/audit/checks/duplicates.ts +3 -1
  383. package/src/audit/checks/i18n.ts +1 -1
  384. package/src/audit/checks/indexability.ts +7 -7
  385. package/src/audit/checks/links.ts +2 -2
  386. package/src/audit/checks/llms.ts +6 -6
  387. package/src/audit/checks/network.ts +3 -3
  388. package/src/audit/checks/og-image.ts +2 -2
  389. package/src/audit/checks/robots.ts +1 -1
  390. package/src/audit/checks/sitemap.ts +2 -2
  391. package/src/audit/checks/social.ts +1 -1
  392. package/src/audit/run.ts +4 -3
  393. package/src/audit/terms.ts +31 -0
  394. package/src/audit/url.ts +13 -13
  395. package/src/cli/command-meta.ts +10 -0
  396. package/src/cli/commands/audit.ts +39 -20
  397. package/src/cli/commands/build.ts +71 -314
  398. package/src/cli/commands/check.ts +1 -0
  399. package/src/cli/commands/dev.ts +40 -1
  400. package/src/cli/commands/doctor.ts +90 -14
  401. package/src/cli/commands/eject.ts +44 -7
  402. package/src/cli/commands/eval.ts +2 -2
  403. package/src/cli/commands/init.ts +161 -41
  404. package/src/cli/commands/migrate.ts +121 -0
  405. package/src/cli/commands/preview.ts +7 -1
  406. package/src/cli/commands/translate.ts +2 -2
  407. package/src/cli/commands/upgrade.ts +141 -0
  408. package/src/cli/commands/version.ts +57 -44
  409. package/src/cli/eject-scripts.ts +121 -6
  410. package/src/cli/index.ts +11 -1
  411. package/src/cli/init/install.ts +70 -0
  412. package/src/cli/init/questions.ts +7 -0
  413. package/src/cli/init/scaffold.ts +459 -75
  414. package/src/cli/lazy-command.ts +37 -1
  415. package/src/cli/prepare.ts +40 -6
  416. package/src/cli/required-secrets.ts +38 -11
  417. package/src/cli/unknown-flags.ts +266 -0
  418. package/src/cli/yarn-pnp.ts +52 -0
  419. package/src/components/content/AccordionItem.astro +7 -1
  420. package/src/components/content/Card.astro +4 -3
  421. package/src/components/content/ColorItem.astro +22 -4
  422. package/src/components/content/GithubInfo.astro +2 -2
  423. package/src/components/content/Prompt.astro +25 -25
  424. package/src/components/content/Tabs.astro +3 -1
  425. package/src/components/content/Tile.astro +2 -3
  426. package/src/components/content/Tooltip.astro +57 -9
  427. package/src/components/content/content-strings.ts +34 -0
  428. package/src/components/content/diff.ts +1 -1
  429. package/src/components/content/mermaid-element.ts +13 -2
  430. package/src/components/content/prompt-markdown.ts +292 -0
  431. package/src/components/content/tooltip-id.ts +41 -0
  432. package/src/components/islands/hooks.ts +75 -3
  433. package/src/components/layout/Analytics.astro +21 -79
  434. package/src/components/layout/Banner.astro +3 -1
  435. package/src/components/layout/DiscoveryLinks.astro +69 -0
  436. package/src/components/layout/Header.astro +83 -20
  437. package/src/components/layout/LanguageSwitcher.astro +4 -1
  438. package/src/components/layout/Logo.astro +23 -2
  439. package/src/components/layout/NavSelector.astro +13 -2
  440. package/src/components/layout/NavTree.astro +12 -3
  441. package/src/components/layout/NavTreeScript.astro +17 -3
  442. package/src/components/layout/PageActions.astro +3 -3
  443. package/src/components/layout/PageFeedback.astro +11 -1
  444. package/src/components/layout/PageLayout.astro +52 -5
  445. package/src/components/layout/ReferenceLayout.astro +21 -12
  446. package/src/components/layout/RootLayout.astro +69 -43
  447. package/src/components/layout/Search.astro +30 -6
  448. package/src/components/layout/WebMcp.astro +1 -1
  449. package/src/components/layout/analytics-client.ts +101 -15
  450. package/src/components/layout/drawer-inert.ts +113 -15
  451. package/src/components/layout/dropdown-clamp.ts +105 -0
  452. package/src/components/layout/head-scripts.ts +15 -6
  453. package/src/components/layout/nav-utils.ts +17 -0
  454. package/src/components/layout/search/algolia.ts +8 -8
  455. package/src/components/layout/search/orama-cloud.ts +9 -7
  456. package/src/components/layout/search/typesense.ts +11 -17
  457. package/src/components/openapi/MessageComposer.astro +2 -2
  458. package/src/components/openapi/Playground.astro +2 -2
  459. package/src/components/openapi/playground-client.ts +79 -10
  460. package/src/core/changelog-index.ts +24 -0
  461. package/src/core/component-overrides.ts +399 -154
  462. package/src/core/config-input.ts +212 -543
  463. package/src/core/config.ts +130 -41
  464. package/src/core/custom-pages.ts +105 -0
  465. package/src/core/data.ts +32 -11
  466. package/src/core/define-components.ts +12 -9
  467. package/src/core/deployment-env.ts +18 -74
  468. package/src/core/diagnostics.ts +14 -7
  469. package/src/core/graph.ts +69 -25
  470. package/src/core/i18n-ui.ts +6 -2
  471. package/src/core/i18n.ts +17 -0
  472. package/src/core/includes.ts +156 -38
  473. package/src/core/last-modified.ts +6 -11
  474. package/src/core/links.ts +166 -2
  475. package/src/core/manifest.ts +10 -3
  476. package/src/core/navigation.ts +137 -19
  477. package/src/core/new-tab.ts +35 -0
  478. package/src/core/node-require.ts +21 -0
  479. package/src/core/project-graph.ts +42 -26
  480. package/src/core/project.ts +9 -4
  481. package/src/core/request-body.ts +61 -0
  482. package/src/core/safe-href.ts +28 -0
  483. package/src/core/safe-links.ts +68 -0
  484. package/src/core/schema.ts +531 -721
  485. package/src/core/server-features.ts +8 -9
  486. package/src/core/sources/assets.ts +47 -11
  487. package/src/core/sources/collection.ts +67 -0
  488. package/src/core/sources/contentful-rich-text.ts +285 -0
  489. package/src/core/sources/contentful.ts +173 -0
  490. package/src/core/sources/github-releases.ts +51 -1
  491. package/src/core/sources/json.ts +71 -0
  492. package/src/core/sources/lexical.ts +195 -0
  493. package/src/core/sources/lower.ts +226 -0
  494. package/src/core/sources/normalize.ts +52 -2
  495. package/src/core/sources/notion.ts +39 -28
  496. package/src/core/sources/payload.ts +135 -0
  497. package/src/core/sources/portable-text.ts +11 -16
  498. package/src/core/sources/remote.ts +226 -0
  499. package/src/core/sources/resolve.ts +104 -166
  500. package/src/core/sources/sanity.ts +16 -49
  501. package/src/core/sources/strapi-blocks.ts +124 -0
  502. package/src/core/sources/strapi.ts +191 -0
  503. package/src/core/sources/types.ts +12 -1
  504. package/src/core/types.ts +20 -1
  505. package/src/core/ui-packs/ar.ts +6 -2
  506. package/src/core/ui-packs/bg.ts +6 -2
  507. package/src/core/ui-packs/bn.ts +6 -2
  508. package/src/core/ui-packs/ca.ts +3 -1
  509. package/src/core/ui-packs/cs.ts +6 -2
  510. package/src/core/ui-packs/da.ts +6 -2
  511. package/src/core/ui-packs/de.ts +6 -2
  512. package/src/core/ui-packs/el.ts +3 -1
  513. package/src/core/ui-packs/es.ts +3 -1
  514. package/src/core/ui-packs/fa.ts +6 -2
  515. package/src/core/ui-packs/fi.ts +6 -2
  516. package/src/core/ui-packs/fr.ts +3 -1
  517. package/src/core/ui-packs/he.ts +6 -2
  518. package/src/core/ui-packs/hi.ts +6 -2
  519. package/src/core/ui-packs/hr.ts +6 -2
  520. package/src/core/ui-packs/hu.ts +6 -2
  521. package/src/core/ui-packs/id.ts +6 -2
  522. package/src/core/ui-packs/it.ts +3 -1
  523. package/src/core/ui-packs/ja.ts +3 -1
  524. package/src/core/ui-packs/ko.ts +3 -1
  525. package/src/core/ui-packs/nl.ts +6 -2
  526. package/src/core/ui-packs/no.ts +6 -2
  527. package/src/core/ui-packs/pl.ts +6 -2
  528. package/src/core/ui-packs/pt-br.ts +3 -1
  529. package/src/core/ui-packs/pt.ts +3 -1
  530. package/src/core/ui-packs/ro.ts +6 -2
  531. package/src/core/ui-packs/ru.ts +6 -2
  532. package/src/core/ui-packs/sk.ts +6 -2
  533. package/src/core/ui-packs/sr.ts +6 -2
  534. package/src/core/ui-packs/sv.ts +6 -2
  535. package/src/core/ui-packs/th.ts +3 -1
  536. package/src/core/ui-packs/tr.ts +6 -2
  537. package/src/core/ui-packs/uk.ts +6 -2
  538. package/src/core/ui-packs/vi.ts +3 -1
  539. package/src/core/ui-packs/zh-tw.ts +3 -1
  540. package/src/core/ui-packs/zh.ts +3 -1
  541. package/src/core/unrecognized-keys.ts +10 -0
  542. package/src/core/version-cut.ts +116 -8
  543. package/src/deploy/adapter-output.ts +57 -97
  544. package/src/deploy/adapters/cloudflare.ts +39 -0
  545. package/src/deploy/adapters/index.ts +41 -0
  546. package/src/deploy/adapters/netlify.ts +34 -0
  547. package/src/deploy/adapters/node.ts +34 -0
  548. package/src/deploy/adapters/registry.ts +127 -0
  549. package/src/deploy/adapters/types.ts +92 -0
  550. package/src/deploy/adapters/vercel.ts +35 -0
  551. package/src/deploy/artifacts.ts +67 -28
  552. package/src/deploy/cloudflare-negotiation.ts +179 -100
  553. package/src/deploy/function-bundle.ts +18 -3
  554. package/src/deploy/headers.ts +129 -22
  555. package/src/deploy/node-headers.ts +198 -0
  556. package/src/deploy/platforms/cloudflare.ts +312 -0
  557. package/src/deploy/platforms/index.ts +67 -0
  558. package/src/deploy/platforms/netlify.ts +52 -0
  559. package/src/deploy/platforms/node.ts +40 -0
  560. package/src/deploy/platforms/paths.ts +42 -0
  561. package/src/deploy/platforms/static.ts +29 -0
  562. package/src/deploy/platforms/types.ts +112 -0
  563. package/src/deploy/platforms/vercel.ts +193 -0
  564. package/src/deploy/redirects.ts +30 -18
  565. package/src/deploy/robots.ts +3 -3
  566. package/src/deploy/rss.ts +2 -2
  567. package/src/deploy/sitemap.ts +2 -2
  568. package/src/deploy/vercel-negotiation.ts +27 -4
  569. package/src/eval/agents.ts +32 -1
  570. package/src/eval/findings.ts +19 -11
  571. package/src/eval/report.ts +10 -2
  572. package/src/markdown/external-links.ts +65 -0
  573. package/src/markdown/include.ts +45 -27
  574. package/src/markdown/index.ts +34 -9
  575. package/src/markdown/inline-code.ts +12 -6
  576. package/src/markdown/relative-links.ts +324 -0
  577. package/src/markdown/themes.ts +3 -3
  578. package/src/migrate/migrate.ts +149 -0
  579. package/src/openapi/parse.ts +64 -16
  580. package/src/openapi/proxy.ts +62 -10
  581. package/src/openapi/references.ts +147 -147
  582. package/src/openapi/render-mdx.ts +32 -10
  583. package/src/openapi/scalar.ts +15 -13
  584. package/src/openapi/sentence.ts +14 -0
  585. package/src/openapi/source.ts +37 -21
  586. package/src/openapi/spec-dependency-error.ts +15 -0
  587. package/src/reference/asyncapi.ts +83 -0
  588. package/src/reference/graphql.ts +89 -0
  589. package/src/reference/index.ts +40 -0
  590. package/src/reference/openapi.ts +83 -0
  591. package/src/reference/options.ts +201 -0
  592. package/src/reference/scalar.ts +136 -0
  593. package/src/reference/schema.ts +88 -0
  594. package/src/registry/eject.ts +97 -42
  595. package/src/search/adapters/algolia.ts +46 -0
  596. package/src/search/adapters/flexsearch.ts +29 -0
  597. package/src/search/adapters/index.ts +39 -0
  598. package/src/search/adapters/mixedbread.ts +39 -0
  599. package/src/search/adapters/orama-cloud.ts +51 -0
  600. package/src/search/adapters/orama.ts +24 -0
  601. package/src/search/adapters/pagefind.ts +27 -0
  602. package/src/search/adapters/registry.ts +131 -0
  603. package/src/search/adapters/types.ts +46 -0
  604. package/src/search/adapters/typesense.ts +57 -0
  605. package/src/search/build.ts +33 -7
  606. package/src/search/documents.ts +6 -0
  607. package/src/search/sync/algolia.ts +12 -11
  608. package/src/search/sync/index.ts +35 -20
  609. package/src/search/sync/orama-cloud.ts +12 -9
  610. package/src/search/sync/typesense.ts +15 -13
  611. package/src/sources/contentful.ts +63 -0
  612. package/src/sources/custom.ts +38 -0
  613. package/src/sources/filesystem.ts +51 -0
  614. package/src/sources/github-releases.ts +52 -0
  615. package/src/sources/index.ts +60 -0
  616. package/src/sources/mdx-remote.ts +76 -0
  617. package/src/sources/notion.ts +63 -0
  618. package/src/sources/obsidian.ts +38 -0
  619. package/src/sources/payload.ts +60 -0
  620. package/src/sources/registry.ts +182 -0
  621. package/src/sources/sanity.ts +66 -0
  622. package/src/sources/shared.ts +52 -0
  623. package/src/sources/strapi.ts +58 -0
  624. package/src/theme/entry.ts +47 -2
  625. package/src/translate/report.ts +40 -7
  626. package/src/upgrade/upgrade.ts +499 -0
  627. package/dist/cli/chunk-12dxjqk7.js.map +0 -10
  628. package/dist/cli/chunk-196vjxp9.js.map +0 -13
  629. package/dist/cli/chunk-2mzebbbz.js +0 -69
  630. package/dist/cli/chunk-2z47ypj8.js.map +0 -11
  631. package/dist/cli/chunk-30e87n55.js +0 -108
  632. package/dist/cli/chunk-30e87n55.js.map +0 -10
  633. package/dist/cli/chunk-450a7rcr.js +0 -185
  634. package/dist/cli/chunk-450a7rcr.js.map +0 -11
  635. package/dist/cli/chunk-5n7t497w.js.map +0 -40
  636. package/dist/cli/chunk-61j18dwk.js +0 -5342
  637. package/dist/cli/chunk-61j18dwk.js.map +0 -58
  638. package/dist/cli/chunk-88by27n5.js +0 -17
  639. package/dist/cli/chunk-88by27n5.js.map +0 -10
  640. package/dist/cli/chunk-8cd8tj54.js.map +0 -34
  641. package/dist/cli/chunk-aztttvb3.js +0 -381
  642. package/dist/cli/chunk-aztttvb3.js.map +0 -12
  643. package/dist/cli/chunk-cvky9gb2.js.map +0 -11
  644. package/dist/cli/chunk-ejjx8znq.js.map +0 -15
  645. package/dist/cli/chunk-fmceyezb.js +0 -1007
  646. package/dist/cli/chunk-fmceyezb.js.map +0 -13
  647. package/dist/cli/chunk-hdm2dkd2.js.map +0 -48
  648. package/dist/cli/chunk-hs3gbh8p.js.map +0 -10
  649. package/dist/cli/chunk-jbj4qhfw.js +0 -30
  650. package/dist/cli/chunk-jbj4qhfw.js.map +0 -10
  651. package/dist/cli/chunk-mqb2ka8m.js +0 -68
  652. package/dist/cli/chunk-mqb2ka8m.js.map +0 -10
  653. package/dist/cli/chunk-mt76t7dj.js.map +0 -11
  654. package/dist/cli/chunk-q4rae3bg.js +0 -60
  655. package/dist/cli/chunk-q4rae3bg.js.map +0 -10
  656. package/dist/cli/chunk-ra1v2nc2.js +0 -35
  657. package/dist/cli/chunk-ra1v2nc2.js.map +0 -10
  658. package/dist/cli/chunk-t3tj0dgr.js.map +0 -15
  659. package/dist/cli/chunk-tqa1s0k8.js +0 -69
  660. package/dist/cli/chunk-tqa1s0k8.js.map +0 -11
  661. package/dist/cli/chunk-vh9w1sgp.js +0 -73
  662. package/dist/cli/chunk-vh9w1sgp.js.map +0 -10
  663. package/dist/cli/chunk-vkrsvbr5.js +0 -107
  664. package/dist/cli/chunk-vkrsvbr5.js.map +0 -11
  665. package/dist/cli/chunk-vrfp10qk.js +0 -81
  666. package/dist/cli/chunk-vrfp10qk.js.map +0 -10
  667. package/dist/cli/chunk-wjt80jps.js +0 -1049
  668. package/dist/cli/chunk-wjt80jps.js.map +0 -24
  669. package/docs/advanced/api-reference.mdx +0 -240
  670. package/docs/reference/cli.mdx +0 -197
  671. package/skills/blume-migrate/assets/oxfmt@0.55.0.patch +0 -20
  672. package/src/components/content/changelog-element.ts +0 -69
  673. package/src/search/providers.ts +0 -91
  674. /package/dist/cli/{chunk-3w7b2vcx.js.map → chunk-7ez8ny0t.js.map} +0 -0
  675. /package/dist/cli/{chunk-688e0dde.js.map → chunk-88cpgt6h.js.map} +0 -0
  676. /package/dist/cli/{chunk-exeeb35e.js.map → chunk-f7t03s3g.js.map} +0 -0
  677. /package/dist/cli/{chunk-ppfvdcd4.js.map → chunk-zxccj738.js.map} +0 -0
@@ -84,8 +84,11 @@ const kbd = "rounded border border-border bg-muted px-1 py-0.5 font-mono";
84
84
  >
85
85
  <Icon name="search" size={16} />
86
86
  <span class="flex-1 text-start max-lg:hidden">{s.button}</span>
87
- <kbd class="font-mono text-[0.7rem] max-lg:hidden" data-blume-search-kbd
88
- >⌘K</kbd
87
+ {/* A key chord reads left to right in every locale ("⌘K", never "K⌘"). */}
88
+ <kbd
89
+ class="font-mono text-[0.7rem] max-lg:hidden"
90
+ data-blume-search-kbd
91
+ dir="ltr">⌘K</kbd
89
92
  >
90
93
  </button>
91
94
 
@@ -195,8 +198,10 @@ const kbd = "rounded border border-border bg-muted px-1 py-0.5 font-mono";
195
198
  {s.open}
196
199
  </span>
197
200
  <span class="flex items-center gap-1 max-md:hidden">
198
- <kbd class={`${kbd} text-[0.65rem]`} data-blume-search-preview-kbd
199
- >⌘J</kbd
201
+ <kbd
202
+ class={`${kbd} text-[0.65rem]`}
203
+ data-blume-search-preview-kbd
204
+ dir="ltr">⌘J</kbd
200
205
  >
201
206
  {s.preview}
202
207
  </span>
@@ -271,6 +276,26 @@ const kbd = "rounded border border-border bg-muted px-1 py-0.5 font-mono";
271
276
  }
272
277
  };
273
278
 
279
+ // The generated client and the index it fetches are the same on every
280
+ // page (locale and version filters apply per query), but each client-router
281
+ // swap rebuilds the header, and this element with it. Loading once in
282
+ // module scope — bundled modules survive swaps — keeps the first search
283
+ // after a navigation from refetching the index and rebuilding it. A failed
284
+ // load clears the cache, so the next open retries.
285
+ let searchClient: Promise<SearchFn> | undefined;
286
+ const loadSearchClient = (): Promise<SearchFn> => {
287
+ searchClient ??= (async () => {
288
+ try {
289
+ const { createSearch } = await import("blume:search-client");
290
+ return await createSearch();
291
+ } catch (error) {
292
+ searchClient = undefined;
293
+ throw error;
294
+ }
295
+ })();
296
+ return searchClient;
297
+ };
298
+
274
299
  class BlumeSearch extends HTMLElement {
275
300
  dialog!: HTMLDialogElement;
276
301
  input!: HTMLInputElement;
@@ -481,8 +506,7 @@ const kbd = "rounded border border-border bg-muted px-1 py-0.5 font-mono";
481
506
  if (!this.loaded) {
482
507
  this.loading = true;
483
508
  try {
484
- const { createSearch } = await import("blume:search-client");
485
- this.searchFn = await createSearch();
509
+ this.searchFn = await loadSearchClient();
486
510
  // Only latch on success — a transient failure (flaky network
487
511
  // fetching the index) must retry on the next open, not disable
488
512
  // search until a full page reload.
@@ -2,7 +2,7 @@
2
2
  // WebMCP: expose the docs' read-only tools (search, page Markdown, the docs
3
3
  // index) to agentic browsers via the page's model context. Rendered by
4
4
  // PageLayout on every page; the marker element below both gates the script
5
- // (absent when `ai.webmcp` is off, so it returns immediately) and carries the
5
+ // (absent when `agents.webmcp` is off, so it returns immediately) and carries the
6
6
  // two config flags the client needs — importing `blume:data` from the client
7
7
  // script would drag the whole navigation snapshot into the bundle.
8
8
  import data from "blume:data";
@@ -1,25 +1,108 @@
1
1
  /**
2
2
  * Send a custom analytics event to every analytics platform configured in
3
- * `blume.config.ts`. Mirrors the providers wired by `Analytics.astro`: Vercel
4
- * Web Analytics and PostHog are first-class (Cloudflare Web Analytics is too,
5
- * but has no custom-event API to forward to); any other provider added through
6
- * `analytics.scripts` is reached via best-effort global detection or the
7
- * `blume:track` CustomEvent, which fires unconditionally so a project can bridge
8
- * the event to anything. Every call no-ops cleanly when a provider isn't present
9
- * — for example during `blume dev`, where `Analytics.astro` injects nothing.
3
+ * `blume.config.ts`. Mirrors the adapters `Analytics.astro` wires
4
+ * (`src/analytics/*`): each one with a custom-event API is called through the
5
+ * global its snippet defines (Cloudflare and Clearbit have none to forward
6
+ * to), and the `blume:track` CustomEvent fires unconditionally so a project
7
+ * can bridge the event to anything else — a `script()` adapter, say. Every
8
+ * call no-ops cleanly when a provider isn't present — for example during
9
+ * `blume dev`, where `Analytics.astro` injects nothing — and a provider that
10
+ * throws (a consent shim that stubs `gtag` with a raise, a broken snippet) is
11
+ * isolated so it neither starves the providers after it nor surfaces in the
12
+ * feature that reported the event.
10
13
  */
11
14
  import { track as vercelTrack } from "@vercel/analytics";
12
15
 
13
16
  /** Flat, serializable event properties. */
14
17
  export type TrackProps = Record<string, boolean | number | string>;
15
18
 
19
+ /** A Tag Manager data layer: a queue the container drains. */
20
+ interface DataLayer {
21
+ push: (entry: TrackProps & { event: string }) => number;
22
+ }
23
+
16
24
  interface AnalyticsWindow {
25
+ /** The data layer a `googleTagManager()` snippet's container reads. */
26
+ __blumeGtmLayer?: DataLayer;
27
+ _satellite?: { track?: (event: string, props?: TrackProps) => void };
28
+ amplitude?: { track?: (event: string, props?: TrackProps) => void };
29
+ analytics?: { track?: (event: string, props?: TrackProps) => void };
30
+ clarity?: (command: "event", event: string) => void;
31
+ dataLayer?: DataLayer;
32
+ fathom?: { trackEvent?: (event: string) => void };
17
33
  gtag?: (command: "event", event: string, props?: TrackProps) => void;
34
+ heap?: { track?: (event: string, props?: TrackProps) => void };
35
+ hj?: (command: "event", event: string) => void;
36
+ htevents?: { track?: (event: string, props?: TrackProps) => void };
37
+ LogRocket?: { track?: (event: string, props?: TrackProps) => void };
38
+ mixpanel?: { track?: (event: string, props?: TrackProps) => void };
39
+ pirsch?: (event: string, options?: { meta?: TrackProps }) => void;
18
40
  plausible?: (event: string, options?: { props?: TrackProps }) => void;
19
41
  posthog?: { capture?: (event: string, props?: TrackProps) => void };
20
42
  }
21
43
 
22
- export const track = (event: string, props: TrackProps): void => {
44
+ /** Run one provider call; its failure must not reach the others or the caller. */
45
+ const attempt = (send: () => void): void => {
46
+ try {
47
+ send();
48
+ } catch {
49
+ // Analytics never breaks the feature that reported the event.
50
+ }
51
+ };
52
+
53
+ // The SDKs whose loader stub queues calls until the library lands, so the
54
+ // method is there to call from the first paint on.
55
+ const trackQueued = (
56
+ w: AnalyticsWindow,
57
+ event: string,
58
+ props: TrackProps
59
+ ): void => {
60
+ attempt(() => w.posthog?.capture?.(event, props));
61
+ attempt(() => w.mixpanel?.track?.(event, props));
62
+ attempt(() => w.heap?.track?.(event, props));
63
+ attempt(() => w.analytics?.track?.(event, props));
64
+ attempt(() => w.htevents?.track?.(event, props));
65
+ attempt(() => w.amplitude?.track?.(event, props));
66
+ attempt(() => w.LogRocket?.track?.(event, props));
67
+ attempt(() => w._satellite?.track?.(event, props));
68
+ };
69
+
70
+ // The providers reached through a global function or a data layer rather
71
+ // than a queued SDK object.
72
+ const trackGlobals = (
73
+ w: AnalyticsWindow,
74
+ event: string,
75
+ props: TrackProps
76
+ ): void => {
77
+ // Google: `gtag('event')` for the Google tag, and a plain `{ event }` push
78
+ // for a Tag Manager custom-event trigger, into the container's own data
79
+ // layer. Each reads only its own shape, so a site with both configured
80
+ // still sees the event once per product.
81
+ attempt(() => w.gtag?.("event", event, props));
82
+ attempt(() => (w.__blumeGtmLayer ?? w.dataLayer)?.push({ ...props, event }));
83
+ // Privacy-first counters take a name, some with properties.
84
+ attempt(() => w.plausible?.(event, { props }));
85
+ attempt(() => w.fathom?.trackEvent?.(event));
86
+ // Pirsch stringifies `meta` values in place, so it gets its own copy and
87
+ // the `blume:track` listeners still see the original types.
88
+ attempt(() => w.pirsch?.(event, { meta: { ...props } }));
89
+ // Behavior tools take a bare name.
90
+ attempt(() => w.clarity?.("event", event));
91
+ attempt(() => w.hj?.("event", event));
92
+ };
93
+
94
+ /**
95
+ * @param event The event name.
96
+ * @param props Properties every provider receives.
97
+ * @param local Properties only the `blume:track` CustomEvent carries — free
98
+ * text a site may bridge to a provider on its own terms, but that must not
99
+ * reach third parties unasked (a reader's Ask AI question, for instance).
100
+ */
101
+ export const track = (
102
+ event: string,
103
+ props: TrackProps,
104
+ local: TrackProps = {}
105
+ ): void => {
23
106
  // Read through `globalThis` so an SSR/import-time call sees `undefined`
24
107
  // instead of a bare-identifier ReferenceError.
25
108
  const browserWindow = globalThis.window;
@@ -31,12 +114,15 @@ export const track = (event: string, props: TrackProps): void => {
31
114
  const w = browserWindow as typeof browserWindow & AnalyticsWindow;
32
115
 
33
116
  // Vercel Web Analytics — self-gates to a no-op until `window.va` is set up.
34
- vercelTrack(event, props);
35
- // PostHog — the injected array.js stub queues calls until the lib loads.
36
- w.posthog?.capture?.(event, props);
37
- // Popular providers wired through `analytics.scripts` (GA4/GTM, Plausible).
38
- w.gtag?.("event", event, props);
39
- w.plausible?.(event, { props });
117
+ attempt(() => vercelTrack(event, props));
118
+ trackQueued(w, event, props);
119
+ trackGlobals(w, event, props);
40
120
  // Universal hook for any other integration.
41
- w.dispatchEvent(new CustomEvent("blume:track", { detail: { event, props } }));
121
+ attempt(() =>
122
+ w.dispatchEvent(
123
+ new CustomEvent("blume:track", {
124
+ detail: { event, props: { ...props, ...local } },
125
+ })
126
+ )
127
+ );
42
128
  };
@@ -1,36 +1,134 @@
1
1
  /**
2
- * Keep the mobile nav drawer out of the tab order while it is closed. The
3
- * closed drawer is only translated off-canvas, so its links would otherwise
4
- * stay focusable on every page. Mirrors the header's `data-blume-nav-open`
5
- * toggle into `inert`/`aria-hidden` — but only below `lg` (64rem), where the
6
- * same element isn't the static sidebar (RootLayout) or is display-hidden
7
- * anyway (PageLayout). Shared by both layouts' bundled scripts, which run once
8
- * per real page load; the drawer element is rebuilt by every client-router
9
- * swap, so `sync` re-queries it each time and re-runs on `astro:after-swap`.
2
+ * The mobile nav drawer's accessibility. The header's click script opens and
3
+ * closes the drawer by toggling `data-blume-nav-open` on `<html>`; everything
4
+ * here follows that attribute, only below `lg` (64rem) — at `lg` and up the same
5
+ * element is the static sidebar (RootLayout) or display-hidden (PageLayout):
6
+ *
7
+ * - Closed, the drawer is only translated off-canvas, so its links would stay
8
+ * focusable on every page: it's `inert`/`aria-hidden` instead.
9
+ * - Open, it's a modal panel over the page: the page behind it (everything but
10
+ * the header, which holds the toggle, and the overlay button) goes `inert`,
11
+ * so Tab can't wander into content the overlay covers, and the header's
12
+ * toggle reports `aria-expanded`.
13
+ * - Escape closes it, and closing it with focus inside (or on the overlay's
14
+ * close button, which disappears) returns focus to the toggle.
15
+ *
16
+ * Shared by both layouts' bundled scripts, which run once per real page load;
17
+ * the drawer and the page behind it are rebuilt by every client-router swap, so
18
+ * `sync` re-queries them each time and re-runs on `astro:after-swap`.
10
19
  */
20
+
21
+ const OPEN_ATTRIBUTE = "data-blume-nav-open";
22
+
23
+ /**
24
+ * Marks what `sync` made inert behind the open drawer, so closing it restores
25
+ * only those — never an element something else made inert.
26
+ */
27
+ const BACKDROP_ATTRIBUTE = "data-blume-drawer-backdrop";
28
+
29
+ /** What stays live beside the open drawer: the header and the overlay. */
30
+ const KEEP_LIVE =
31
+ "[data-blume-header], [data-blume-nav-toggle], script, style, template";
32
+
33
+ const drawerElement = (): HTMLElement | null =>
34
+ document.querySelector<HTMLElement>("[data-blume-nav-drawer]");
35
+
36
+ const headerToggle = (): HTMLElement | null =>
37
+ document.querySelector<HTMLElement>(
38
+ "[data-blume-header] [data-blume-nav-toggle]"
39
+ );
40
+
41
+ /**
42
+ * The page behind the drawer: every sibling along the drawer's path up to
43
+ * `<body>`, minus what stays live.
44
+ */
45
+ const backdropOf = (drawer: Element): Element[] => {
46
+ const backdrop: Element[] = [];
47
+ let node = drawer;
48
+ while (node !== document.body && node.parentElement) {
49
+ for (const sibling of node.parentElement.children) {
50
+ if (sibling !== node && !sibling.matches(KEEP_LIVE)) {
51
+ backdrop.push(sibling);
52
+ }
53
+ }
54
+ node = node.parentElement;
55
+ }
56
+ return backdrop;
57
+ };
58
+
59
+ const restoreBackdrop = (): void => {
60
+ for (const element of document.querySelectorAll(`[${BACKDROP_ATTRIBUTE}]`)) {
61
+ element.removeAttribute("inert");
62
+ element.removeAttribute(BACKDROP_ATTRIBUTE);
63
+ }
64
+ };
65
+
66
+ const inertBackdrop = (drawer: Element): void => {
67
+ for (const element of backdropOf(drawer)) {
68
+ // Already inert for another reason: leave it, and leave it unmarked.
69
+ if (!element.hasAttribute("inert")) {
70
+ element.setAttribute("inert", "");
71
+ element.setAttribute(BACKDROP_ATTRIBUTE, "");
72
+ }
73
+ }
74
+ };
75
+
11
76
  export const syncDrawerInert = (): void => {
12
77
  const desktop = window.matchMedia("(min-width: 64rem)");
78
+ const isOpen = () =>
79
+ !desktop.matches && document.documentElement.hasAttribute(OPEN_ATTRIBUTE);
80
+ let wasOpen = false;
81
+
13
82
  const sync = () => {
14
- const drawer = document.querySelector<HTMLElement>(
15
- "[data-blume-nav-drawer]"
16
- );
83
+ const open = isOpen();
84
+ headerToggle()?.setAttribute("aria-expanded", open ? "true" : "false");
85
+ const drawer = drawerElement();
17
86
  if (!drawer) {
87
+ restoreBackdrop();
88
+ wasOpen = open;
18
89
  return;
19
90
  }
20
- const hidden =
21
- !desktop.matches &&
22
- !Object.hasOwn(document.documentElement.dataset, "blumeNavOpen");
91
+ const hidden = !(desktop.matches || open);
23
92
  drawer.inert = hidden;
24
93
  if (hidden) {
25
94
  drawer.setAttribute("aria-hidden", "true");
26
95
  } else {
27
96
  drawer.removeAttribute("aria-hidden");
28
97
  }
98
+ if (open) {
99
+ inertBackdrop(drawer);
100
+ } else {
101
+ restoreBackdrop();
102
+ const active = document.activeElement;
103
+ if (
104
+ wasOpen &&
105
+ !desktop.matches &&
106
+ active &&
107
+ (drawer.contains(active) || active.matches("[data-blume-nav-toggle]"))
108
+ ) {
109
+ headerToggle()?.focus();
110
+ }
111
+ }
112
+ wasOpen = open;
29
113
  };
114
+
30
115
  sync();
31
116
  desktop.addEventListener("change", sync);
32
117
  new MutationObserver(sync).observe(document.documentElement, {
33
- attributeFilter: ["data-blume-nav-open"],
118
+ attributeFilter: [OPEN_ATTRIBUTE],
34
119
  });
35
120
  document.addEventListener("astro:after-swap", sync);
121
+ document.addEventListener("keydown", (event) => {
122
+ // An open dialog (search, Ask AI) closes itself on Escape first.
123
+ if (
124
+ event.key !== "Escape" ||
125
+ !isOpen() ||
126
+ document.querySelector("dialog[open]")
127
+ ) {
128
+ return;
129
+ }
130
+ document.documentElement.removeAttribute(OPEN_ATTRIBUTE);
131
+ sync();
132
+ headerToggle()?.focus();
133
+ });
36
134
  };
@@ -0,0 +1,105 @@
1
+ /**
2
+ * Keep a header selector's dropdown panel inside the viewport. The panel is
3
+ * absolutely positioned against its trigger (`end-0` for the version
4
+ * selector), which is right on desktop, where the trigger sits at the header's
5
+ * trailing edge — but on a phone the trigger lands mid-header, so a 14rem
6
+ * panel anchored to its end runs off the leading edge (and, mirrored, off the
7
+ * trailing edge in a right-to-left locale). When a panel opens, it is measured
8
+ * and nudged back inside with `translate`, which leaves the anchoring classes
9
+ * — and the desktop layout — untouched.
10
+ *
11
+ * One capture-phase `toggle` listener on `document` (the event doesn't bubble)
12
+ * serves every opted-in dropdown, installed once per real page load: the
13
+ * client router rebuilds the header markup on each swap, and delegation needs
14
+ * no re-binding.
15
+ */
16
+
17
+ /** Gap kept between a clamped panel and the viewport edge, in pixels. */
18
+ const MARGIN = 8;
19
+
20
+ /** The panel an opted-in `<details>` dropdown clamps. */
21
+ export const CLAMPED_PANEL = "[data-blume-dropdown-panel]";
22
+
23
+ /**
24
+ * The horizontal shift that brings a panel spanning `left`…`right` inside a
25
+ * viewport `width` wide, keeping `margin` at each edge. A panel wider than the
26
+ * room available pins its leading edge (the one reading starts from) to the
27
+ * margin.
28
+ */
29
+ export const clampShift = (
30
+ left: number,
31
+ right: number,
32
+ width: number,
33
+ rtl: boolean,
34
+ margin = MARGIN
35
+ ): number => {
36
+ const tooWide = right - left > width - 2 * margin;
37
+ if (tooWide) {
38
+ return rtl ? width - margin - right : margin - left;
39
+ }
40
+ if (left < margin) {
41
+ return margin - left;
42
+ }
43
+ if (right > width - margin) {
44
+ return width - margin - right;
45
+ }
46
+ return 0;
47
+ };
48
+
49
+ /** The slice of a panel element the clamp measures and moves. */
50
+ export interface PanelBox {
51
+ getBoundingClientRect: () => { left: number; right: number };
52
+ style: { translate: string };
53
+ }
54
+
55
+ /** Measure `panel` in place and shift it back inside the viewport. */
56
+ export const clampPanel = (panel: PanelBox): void => {
57
+ panel.style.translate = "";
58
+ const { left, right } = panel.getBoundingClientRect();
59
+ const root = document.documentElement;
60
+ const shift = clampShift(
61
+ left,
62
+ right,
63
+ root.clientWidth,
64
+ root.getAttribute("dir") === "rtl"
65
+ );
66
+ if (shift !== 0) {
67
+ panel.style.translate = `${shift}px 0`;
68
+ }
69
+ };
70
+
71
+ /** The clamped panel of the dropdown a `toggle` event opened, if any. */
72
+ const openedPanel = (target: EventTarget | null): HTMLElement | null =>
73
+ target instanceof HTMLDetailsElement && target.open
74
+ ? target.querySelector<HTMLElement>(CLAMPED_PANEL)
75
+ : null;
76
+
77
+ /** Clamp every open panel again, after the viewport changed size. */
78
+ const reclampOpen = (): void => {
79
+ for (const panel of document.querySelectorAll<HTMLElement>(
80
+ `details[open] > ${CLAMPED_PANEL}`
81
+ )) {
82
+ clampPanel(panel);
83
+ }
84
+ };
85
+
86
+ let installed = false;
87
+
88
+ /** Clamp opted-in dropdown panels on open (and on resize while open). */
89
+ export const installDropdownClamp = (): void => {
90
+ if (installed) {
91
+ return;
92
+ }
93
+ installed = true;
94
+ document.addEventListener(
95
+ "toggle",
96
+ (event) => {
97
+ const panel = openedPanel(event.target);
98
+ if (panel) {
99
+ clampPanel(panel);
100
+ }
101
+ },
102
+ true
103
+ );
104
+ window.addEventListener("resize", reclampOpen);
105
+ };
@@ -39,9 +39,13 @@
39
39
  * the `after-swap` re-apply stays as the fallback that also picks up a
40
40
  * preference changed in another tab.
41
41
  *
42
- * Reads `data-mode` — `"system" | "light" | "dark"`.
42
+ * Reads `data-mode` — `"system" | "light" | "dark"`. Blocked storage (Safari
43
+ * "Block All Cookies", sandboxed iframes) throws on read. That reads as no
44
+ * stored preference instead of aborting before the swap listeners exist, and
45
+ * after a swap the theme the `before-swap` stamp carried over stands, so a
46
+ * toggle the reader made still holds for the rest of their visit.
43
47
  */
44
- export const THEME_INIT_SCRIPT = `(()=>{const m=document.currentScript?.dataset.mode??"system";const apply=()=>{const s=localStorage.getItem("blume-theme");const sys=matchMedia("(prefers-color-scheme: dark)").matches?"dark":"light";document.documentElement.dataset.theme=s??(m==="system"?sys:m);};apply();document.addEventListener("astro:before-swap",(e)=>{const t=document.documentElement.dataset.theme;if(t){e.newDocument.documentElement.dataset.theme=t;}});document.addEventListener("astro:after-swap",apply);})();`;
48
+ export const THEME_INIT_SCRIPT = `(()=>{const m=document.currentScript?.dataset.mode??"system";const apply=()=>{const r=document.documentElement;let s=null;let blocked=false;try{s=localStorage.getItem("blume-theme");}catch{blocked=true;}const sys=matchMedia("(prefers-color-scheme: dark)").matches?"dark":"light";r.dataset.theme=s??(blocked&&r.dataset.theme?r.dataset.theme:m==="system"?sys:m);};apply();document.addEventListener("astro:before-swap",(e)=>{const t=document.documentElement.dataset.theme;if(t){e.newDocument.documentElement.dataset.theme=t;}});document.addEventListener("astro:after-swap",apply);})();`;
45
49
 
46
50
  /**
47
51
  * Keep an embedded Scalar reference on Blume's theme. Scalar picks its own
@@ -63,12 +67,17 @@ export const THEME_INIT_SCRIPT = `(()=>{const m=document.currentScript?.dataset.
63
67
  export const SCALAR_THEME_INIT_SCRIPT = `(()=>{const d=document.documentElement;const mode=()=>d.dataset.theme==="dark"?"dark":"light";let pinned=0;for(const el of document.querySelectorAll("[data-scalar-client]")){let c;try{c=JSON.parse(el.dataset.configuration||"{}");}catch{continue;}if("forceDarkModeState" in c||"darkMode" in c){continue;}c.forceDarkModeState=mode();el.dataset.configuration=JSON.stringify(c);pinned+=1;}if(!pinned){return;}new MutationObserver(()=>{const dark=mode()==="dark";document.body.classList.toggle("dark-mode",dark);document.body.classList.toggle("light-mode",!dark);}).observe(d,{attributes:true,attributeFilter:["data-theme"]});})();`;
64
68
 
65
69
  /**
66
- * Hide a previously-dismissed banner before it can flash in — and again after
67
- * every client-router swap, which wipes the `<html>` marker attribute.
70
+ * Hide a previously-dismissed banner before it can flash in — and keep it
71
+ * hidden across client-router swaps, which replace the `<html>` attributes with
72
+ * the incoming page's: `astro:before-swap` carries the marker onto the incoming
73
+ * root (as the theme script does with `data-theme`), and `after-swap` re-reads
74
+ * storage as the fallback.
68
75
  *
69
- * Reads `data-key` — the banner's dismissal key.
76
+ * Reads `data-key` — the banner's dismissal key. A storage read that throws
77
+ * (blocked storage) reads as not dismissed; a dismissal made on this visit
78
+ * still holds across its swaps through the carried-over marker.
70
79
  */
71
- export const BANNER_INIT_SCRIPT = `(()=>{const k=document.currentScript?.dataset.key;if(!k){return;}const apply=()=>{if(localStorage.getItem("blume-banner:"+k)){document.documentElement.setAttribute("data-blume-banner-hidden","");}};apply();document.addEventListener("astro:after-swap",apply);})();`;
80
+ export const BANNER_INIT_SCRIPT = `(()=>{const k=document.currentScript?.dataset.key;if(!k){return;}const apply=()=>{let hidden=null;try{hidden=localStorage.getItem("blume-banner:"+k);}catch{}if(hidden){document.documentElement.setAttribute("data-blume-banner-hidden","");}};apply();document.addEventListener("astro:before-swap",(e)=>{if(document.documentElement.hasAttribute("data-blume-banner-hidden")){e.newDocument.documentElement.setAttribute("data-blume-banner-hidden","");}});document.addEventListener("astro:after-swap",apply);})();`;
72
81
 
73
82
  /**
74
83
  * Keep the page styled across client-router swaps. Astro hoists the CSS of a
@@ -14,6 +14,23 @@ export interface Crumb {
14
14
  route?: string;
15
15
  }
16
16
 
17
+ /**
18
+ * Whether a group's header row reads as the current page. A routed group links
19
+ * to its folder's index page; when that index is also listed as one of the
20
+ * group's own rows (kept under a different label), the row is the more
21
+ * specific indicator, so the header stays quiet rather than lighting up beside
22
+ * it. With the index row hidden the header is the only link and takes the
23
+ * highlight.
24
+ */
25
+ export const isGroupRowCurrent = (
26
+ group: Extract<NavNode, { kind: "group" }>,
27
+ currentRoute: string
28
+ ): boolean =>
29
+ group.route === currentRoute &&
30
+ !group.children.some(
31
+ (child) => child.kind === "page" && child.route === currentRoute
32
+ );
33
+
17
34
  /** Flatten the sidebar tree into ordered internal page links. */
18
35
  export const flattenPages = (nodes: NavNode[]): FlatPage[] => {
19
36
  const out: FlatPage[] = [];
@@ -1,5 +1,6 @@
1
1
  import { liteClient } from "algoliasearch/lite";
2
2
 
3
+ import type { AlgoliaOptions } from "../../../search/adapters/algolia.ts";
3
4
  import { excerptFor, highlight, SEARCH_LIMIT } from "./types.ts";
4
5
  import type { SearchFn } from "./types.ts";
5
6
 
@@ -13,20 +14,19 @@ interface AlgoliaRecord {
13
14
 
14
15
  /**
15
16
  * Algolia: the browser queries the index directly with the public,
16
- * search-only key. Records are uploaded at build time by the sync step.
17
+ * search-only key. Records are uploaded at build time by the sync step. Every
18
+ * adapter option Blume doesn't read (`hosts`, `timeouts`, `baseHeaders`, …) is
19
+ * the site's own client option and goes to `liteClient` untouched.
17
20
  */
18
- export const createSearch = (opts: {
19
- appId: string;
20
- indexName: string;
21
- searchApiKey: string;
22
- }): SearchFn => {
23
- const client = liteClient(opts.appId, opts.searchApiKey);
21
+ export const createSearch = (opts: AlgoliaOptions): SearchFn => {
22
+ const { apiKey, appId, indexName, ...clientOptions } = opts;
23
+ const client = liteClient(appId, apiKey, clientOptions);
24
24
  return async (query, options) => {
25
25
  const { results } = await client.search<AlgoliaRecord>({
26
26
  requests: [
27
27
  {
28
28
  hitsPerPage: SEARCH_LIMIT,
29
- indexName: opts.indexName,
29
+ indexName,
30
30
  query,
31
31
  // The sync uploads `locale` and `version` on every record so a
32
32
  // site can scope hosted results to the active language and the
@@ -1,5 +1,6 @@
1
1
  import { OramaClient } from "@oramacloud/client";
2
2
 
3
+ import type { OramaCloudOptions } from "../../../search/adapters/orama-cloud.ts";
3
4
  import { excerptFor, highlight, SEARCH_LIMIT } from "./types.ts";
4
5
  import type { SearchFn } from "./types.ts";
5
6
 
@@ -12,15 +13,16 @@ interface OramaCloudRecord {
12
13
 
13
14
  /**
14
15
  * Orama Cloud: the browser queries the hosted index directly with the public
15
- * endpoint + API key. Records are pushed at build time by the sync step.
16
+ * endpoint + API key. Records are pushed at build time by the sync step. Every
17
+ * adapter option Blume doesn't read is the site's own client option and goes
18
+ * to `OramaClient` untouched; `indexId` belongs to the sync and stays out.
16
19
  */
17
- export const createSearch = (opts: {
18
- endpoint: string;
19
- apiKey: string;
20
- }): SearchFn => {
20
+ export const createSearch = (opts: OramaCloudOptions): SearchFn => {
21
+ const { apiKey, endpoint, indexId: _syncOnly, ...clientOptions } = opts;
21
22
  const client = new OramaClient({
22
- api_key: opts.apiKey,
23
- endpoint: opts.endpoint,
23
+ ...clientOptions,
24
+ api_key: apiKey,
25
+ endpoint,
24
26
  });
25
27
  return async (query, options) => {
26
28
  const results = await client.search({
@@ -1,5 +1,6 @@
1
1
  import { Client } from "typesense";
2
2
 
3
+ import type { TypesenseOptions } from "../../../search/adapters/typesense.ts";
3
4
  import { excerptFor, highlight, SEARCH_LIMIT } from "./types.ts";
4
5
  import type { SearchFn } from "./types.ts";
5
6
 
@@ -13,28 +14,21 @@ interface TypesenseRecord extends Record<string, unknown> {
13
14
 
14
15
  /**
15
16
  * Typesense: the browser queries the collection directly with the search-only
16
- * key. Documents are imported at build time by the sync step.
17
+ * key. Documents are imported at build time by the sync step. Every adapter
18
+ * option Blume doesn't read (`connectionTimeoutSeconds`, `numRetries`, …) is
19
+ * the site's own client option and goes to the `Client` untouched; the named
20
+ * connection options still decide `apiKey` and `nodes`.
17
21
  */
18
- export const createSearch = (opts: {
19
- collection: string;
20
- host: string;
21
- port?: number;
22
- protocol?: string;
23
- searchApiKey: string;
24
- }): SearchFn => {
22
+ export const createSearch = (opts: TypesenseOptions): SearchFn => {
23
+ const { apiKey, collection, host, port, protocol, ...clientOptions } = opts;
25
24
  const client = new Client({
26
- apiKey: opts.searchApiKey,
27
- nodes: [
28
- {
29
- host: opts.host,
30
- port: opts.port ?? 443,
31
- protocol: opts.protocol ?? "https",
32
- },
33
- ],
25
+ ...clientOptions,
26
+ apiKey,
27
+ nodes: [{ host, port: port ?? 443, protocol: protocol ?? "https" }],
34
28
  });
35
29
  return async (query, options) => {
36
30
  const response = await client
37
- .collections<TypesenseRecord>(opts.collection)
31
+ .collections<TypesenseRecord>(collection)
38
32
  .documents()
39
33
  .search(
40
34
  {
@@ -1,5 +1,5 @@
1
1
  ---
2
- import { randomUUID } from "node:crypto";
2
+ import { stableId } from "../content/tooltip-id.ts";
3
3
 
4
4
  import type { MessageModel } from "./message.ts";
5
5
 
@@ -28,7 +28,7 @@ const modelJson = JSON.stringify(model).replaceAll("<", "\\u003c");
28
28
 
29
29
  // Unique per rendered composer (a page can hold several), so the textarea's
30
30
  // aria-describedby can point at ITS error container and no other.
31
- const payloadErrorsId = `blume-payload-errors-${randomUUID()}`;
31
+ const payloadErrorsId = stableId(Astro.locals, "blume-payload-errors", [modelJson]);
32
32
 
33
33
  const LABEL_ROW =
34
34
  "mb-1 flex flex-wrap items-baseline gap-x-1.5 font-medium text-foreground text-xs";