blume 2.0.2 → 2.1.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 (601) hide show
  1. package/AGENTS.md +1 -0
  2. package/CHANGELOG.md +113 -0
  3. package/README.md +1 -1
  4. package/dist/cli/{chunk-6k4ftwze.js → chunk-0gz36akr.js} +18 -2
  5. package/dist/cli/{chunk-6k4ftwze.js.map → chunk-0gz36akr.js.map} +3 -3
  6. package/dist/cli/{chunk-zp79m0ts.js → chunk-1n228xqb.js} +2 -2
  7. package/dist/cli/{chunk-ddndchfr.js → chunk-2f3ndkpw.js} +57 -29
  8. package/dist/cli/chunk-2f3ndkpw.js.map +14 -0
  9. package/dist/cli/{chunk-fsmrqk8a.js → chunk-2f7n4qzt.js} +8 -2
  10. package/dist/cli/{chunk-fsmrqk8a.js.map → chunk-2f7n4qzt.js.map} +3 -3
  11. package/dist/cli/{chunk-ppzjqwx2.js → chunk-2n4ps1b3.js} +17 -18
  12. package/dist/cli/{chunk-ppzjqwx2.js.map → chunk-2n4ps1b3.js.map} +3 -3
  13. package/dist/cli/{chunk-5r8g91qn.js → chunk-43zcf4gz.js} +398 -159
  14. package/dist/cli/chunk-43zcf4gz.js.map +36 -0
  15. package/dist/cli/chunk-4ejxbzee.js +39 -0
  16. package/dist/cli/chunk-4ejxbzee.js.map +10 -0
  17. package/dist/cli/{chunk-hr8ne106.js → chunk-4h8z9vv5.js} +296 -77
  18. package/dist/cli/chunk-4h8z9vv5.js.map +16 -0
  19. package/dist/cli/{chunk-k7pj68a8.js → chunk-5cg2rzgf.js} +18 -16
  20. package/dist/cli/{chunk-k7pj68a8.js.map → chunk-5cg2rzgf.js.map} +2 -2
  21. package/dist/cli/{chunk-h2ez8dzb.js → chunk-5ffywsmb.js} +4 -4
  22. package/dist/cli/{chunk-g698a744.js → chunk-5wj2qwn2.js} +5 -5
  23. package/dist/cli/chunk-6bm0xpgd.js +18 -0
  24. package/dist/cli/chunk-6bm0xpgd.js.map +10 -0
  25. package/dist/cli/{chunk-3yce002v.js → chunk-713mngdq.js} +15 -8
  26. package/dist/cli/chunk-713mngdq.js.map +14 -0
  27. package/dist/cli/{chunk-91ws1n6j.js → chunk-9geymqns.js} +17 -15
  28. package/dist/cli/{chunk-91ws1n6j.js.map → chunk-9geymqns.js.map} +2 -2
  29. package/dist/cli/{chunk-6dtt0zfn.js → chunk-9xr4dgee.js} +27 -18
  30. package/dist/cli/chunk-9xr4dgee.js.map +10 -0
  31. package/dist/cli/{chunk-sqw4ekg1.js → chunk-amrb6hgh.js} +2 -2
  32. package/dist/cli/chunk-cm89scdv.js +443 -0
  33. package/dist/cli/chunk-cm89scdv.js.map +11 -0
  34. package/dist/cli/{chunk-wdrt2k2v.js → chunk-cmtcnh9f.js} +90 -26
  35. package/dist/cli/chunk-cmtcnh9f.js.map +10 -0
  36. package/dist/cli/chunk-cy5bm0j0.js +104 -0
  37. package/dist/cli/chunk-cy5bm0j0.js.map +11 -0
  38. package/dist/cli/{chunk-7mbqtmgb.js → chunk-e7vvk5ya.js} +21 -12
  39. package/dist/cli/chunk-e7vvk5ya.js.map +10 -0
  40. package/dist/cli/{chunk-qkb5a8sa.js → chunk-g2z9x01v.js} +6 -98
  41. package/dist/cli/chunk-g2z9x01v.js.map +10 -0
  42. package/dist/cli/{chunk-273ygyr4.js → chunk-ga0pf4aj.js} +7 -7
  43. package/dist/cli/{chunk-273ygyr4.js.map → chunk-ga0pf4aj.js.map} +3 -3
  44. package/dist/cli/{chunk-h7k3nq3v.js → chunk-gp84za0h.js} +2 -2
  45. package/dist/cli/{chunk-hqp2ajnh.js → chunk-h2a48yhd.js} +195 -73
  46. package/dist/cli/chunk-h2a48yhd.js.map +36 -0
  47. package/dist/cli/{chunk-pbg5a4s3.js → chunk-jnwj5kjj.js} +71 -47
  48. package/dist/cli/chunk-jnwj5kjj.js.map +19 -0
  49. package/dist/cli/chunk-kwrg63tm.js +31 -0
  50. package/dist/cli/chunk-kwrg63tm.js.map +10 -0
  51. package/dist/cli/{chunk-8g8ytmgx.js → chunk-nd1821sk.js} +21 -19
  52. package/dist/cli/{chunk-8g8ytmgx.js.map → chunk-nd1821sk.js.map} +2 -2
  53. package/dist/cli/{chunk-w4bxdvsa.js → chunk-ntv4qrt7.js} +17 -15
  54. package/dist/cli/{chunk-w4bxdvsa.js.map → chunk-ntv4qrt7.js.map} +2 -2
  55. package/dist/cli/{chunk-bfwp9vp6.js → chunk-p804f3pe.js} +7 -7
  56. package/dist/cli/{chunk-4e9b9ra6.js → chunk-py62fewh.js} +6 -6
  57. package/dist/cli/{chunk-4e9b9ra6.js.map → chunk-py62fewh.js.map} +1 -1
  58. package/dist/cli/chunk-q3wxxq39.js +126 -0
  59. package/dist/cli/chunk-q3wxxq39.js.map +11 -0
  60. package/dist/cli/{chunk-j85scx15.js → chunk-rekqz4qg.js} +142 -46
  61. package/dist/cli/chunk-rekqz4qg.js.map +10 -0
  62. package/dist/cli/{chunk-00gs3wqs.js → chunk-ta27feev.js} +1 -1
  63. package/dist/cli/{chunk-v6ya5kcb.js → chunk-xkz5xbtv.js} +4031 -1132
  64. package/dist/cli/chunk-xkz5xbtv.js.map +213 -0
  65. package/dist/cli/{chunk-bbnwccaz.js → chunk-y4ykbark.js} +2 -2
  66. package/dist/cli/{chunk-n1yg3tj3.js → chunk-y5h4r2y0.js} +6 -6
  67. package/dist/cli/{chunk-n1yg3tj3.js.map → chunk-y5h4r2y0.js.map} +3 -3
  68. package/dist/cli/{chunk-nfcyttvj.js → chunk-yj17vs39.js} +9 -9
  69. package/dist/cli/{chunk-nfcyttvj.js.map → chunk-yj17vs39.js.map} +1 -1
  70. package/dist/cli/{chunk-7vtckvaw.js → chunk-yxmk4pec.js} +16 -14
  71. package/dist/cli/{chunk-7vtckvaw.js.map → chunk-yxmk4pec.js.map} +2 -2
  72. package/dist/cli/{chunk-gs7r695n.js → chunk-zccbjcj0.js} +22 -8
  73. package/dist/cli/{chunk-gs7r695n.js.map → chunk-zccbjcj0.js.map} +3 -3
  74. package/dist/cli/{chunk-xh43dwgw.js → chunk-zf748avv.js} +415 -225
  75. package/dist/cli/chunk-zf748avv.js.map +37 -0
  76. package/dist/cli/{chunk-d1v5rhy0.js → chunk-zhvd6adm.js} +16 -14
  77. package/dist/cli/{chunk-d1v5rhy0.js.map → chunk-zhvd6adm.js.map} +2 -2
  78. package/dist/cli/index.js +28 -20
  79. package/dist/cli/index.js.map +5 -5
  80. package/dist/types/ai/agent-surface.d.ts +8 -7
  81. package/dist/types/ai/ai-catalog.d.ts +7 -0
  82. package/dist/types/ai/ask-context.d.ts +22 -0
  83. package/dist/types/ai/ask.d.ts +271 -58
  84. package/dist/types/ai/index.d.ts +2 -2
  85. package/dist/types/ai/link-headers.d.ts +8 -1
  86. package/dist/types/ai/openapi-components.d.ts +5 -2
  87. package/dist/types/ai/relative-links.d.ts +9 -7
  88. package/dist/types/ai/site-skill.d.ts +26 -0
  89. package/dist/types/ai/skills.d.ts +20 -1
  90. package/dist/types/ai/tar.d.ts +1 -3
  91. package/dist/types/analytics/index.d.ts +2 -0
  92. package/dist/types/analytics/one-dollar-stats.d.ts +48 -0
  93. package/dist/types/analytics/schema.d.ts +14 -0
  94. package/dist/types/astro/integration.d.ts +11 -2
  95. package/dist/types/captcha/hcaptcha.d.ts +27 -0
  96. package/dist/types/captcha/index.d.ts +28 -0
  97. package/dist/types/captcha/schema.d.ts +29 -0
  98. package/dist/types/captcha/turnstile.d.ts +28 -0
  99. package/dist/types/components/content/api-field.d.ts +59 -0
  100. package/dist/types/components/content/api-page.d.ts +95 -0
  101. package/dist/types/components/layout/nav-utils.d.ts +26 -0
  102. package/dist/types/components/openapi/helpers.d.ts +137 -0
  103. package/dist/types/components/openapi/security.d.ts +85 -0
  104. package/dist/types/consent/ethyca.d.ts +48 -0
  105. package/dist/types/consent/index.d.ts +37 -0
  106. package/dist/types/consent/native.d.ts +28 -0
  107. package/dist/types/consent/osano.d.ts +42 -0
  108. package/dist/types/consent/schema.d.ts +110 -0
  109. package/dist/types/core/base-path.d.ts +21 -7
  110. package/dist/types/core/config-input.d.ts +196 -27
  111. package/dist/types/core/data.d.ts +49 -4
  112. package/dist/types/core/directive-diagnostics.d.ts +12 -0
  113. package/dist/types/core/footer.d.ts +8 -0
  114. package/dist/types/core/heading-markers.d.ts +5 -7
  115. package/dist/types/core/i18n-ui.d.ts +86 -0
  116. package/dist/types/core/i18n.d.ts +2 -0
  117. package/dist/types/core/includes.d.ts +8 -2
  118. package/dist/types/core/last-modified.d.ts +10 -0
  119. package/dist/types/core/localizable.d.ts +7 -0
  120. package/dist/types/core/meta.d.ts +8 -0
  121. package/dist/types/core/page-modes.d.ts +29 -0
  122. package/dist/types/core/redirect-patterns.d.ts +144 -0
  123. package/dist/types/core/related.d.ts +40 -0
  124. package/dist/types/core/schema.d.ts +558 -56
  125. package/dist/types/core/sources/lower.d.ts +29 -16
  126. package/dist/types/core/sources/normalize.d.ts +13 -1
  127. package/dist/types/core/sources/read.d.ts +5 -0
  128. package/dist/types/core/sources/watch.d.ts +12 -5
  129. package/dist/types/core/standard-schema.d.ts +5 -0
  130. package/dist/types/core/types.d.ts +1 -1
  131. package/dist/types/core/variables.d.ts +42 -0
  132. package/dist/types/deploy/artifacts.d.ts +6 -4
  133. package/dist/types/deploy/headers.d.ts +5 -0
  134. package/dist/types/deploy/node-headers.d.ts +13 -2
  135. package/dist/types/deploy/platforms/cloudflare.d.ts +5 -0
  136. package/dist/types/deploy/platforms/netlify.d.ts +7 -0
  137. package/dist/types/deploy/platforms/types.d.ts +8 -0
  138. package/dist/types/deploy/platforms/vercel.d.ts +6 -0
  139. package/dist/types/deploy/redirects.d.ts +61 -22
  140. package/dist/types/deploy/vercel-negotiation.d.ts +9 -0
  141. package/dist/types/markdown/code-title.d.ts +17 -0
  142. package/dist/types/markdown/directives.d.ts +62 -0
  143. package/dist/types/markdown/features.d.ts +24 -2
  144. package/dist/types/markdown/mdast.d.ts +63 -0
  145. package/dist/types/narration/build.d.ts +70 -0
  146. package/dist/types/narration/provider.d.ts +26 -0
  147. package/dist/types/narration/script.d.ts +154 -0
  148. package/dist/types/openapi/asyncapi.d.ts +4 -2
  149. package/dist/types/openapi/model.d.ts +12 -6
  150. package/dist/types/openapi/overlay.d.ts +54 -0
  151. package/dist/types/openapi/parse.d.ts +13 -0
  152. package/dist/types/openapi/references.d.ts +14 -4
  153. package/dist/types/ratelimit/cloudflare.d.ts +39 -0
  154. package/dist/types/ratelimit/index.d.ts +27 -0
  155. package/dist/types/ratelimit/memory.d.ts +36 -0
  156. package/dist/types/ratelimit/schema.d.ts +111 -0
  157. package/dist/types/ratelimit/upstash.d.ts +21 -0
  158. package/dist/types/ratelimit/wrangler.d.ts +23 -0
  159. package/dist/types/reference/asyncapi.d.ts +10 -10
  160. package/dist/types/reference/graphql.d.ts +13 -10
  161. package/dist/types/reference/openapi.d.ts +33 -12
  162. package/dist/types/reference/options.d.ts +45 -5
  163. package/dist/types/reference/scalar.d.ts +13 -0
  164. package/dist/types/reference/schema.d.ts +60 -36
  165. package/dist/types/search/documents.d.ts +12 -1
  166. package/dist/types/search/orama-index.d.ts +4 -0
  167. package/dist/types/seo/metatags.d.ts +20 -0
  168. package/docs/02-deployment.mdx +44 -6
  169. package/docs/03-upgrading.mdx +4 -4
  170. package/docs/04-migrating.mdx +1 -1
  171. package/docs/08-faq.mdx +1 -1
  172. package/docs/advanced/changelog.mdx +2 -2
  173. package/docs/advanced/custom-pages.mdx +3 -3
  174. package/docs/advanced/skills.mdx +14 -0
  175. package/docs/cli/audit.mdx +38 -2
  176. package/docs/cli/evals.mdx +3 -3
  177. package/docs/cli/index.mdx +1 -0
  178. package/docs/cli/translate.mdx +3 -3
  179. package/docs/cli/version.mdx +1 -1
  180. package/docs/configuration/analytics.mdx +22 -1
  181. package/docs/configuration/assistant.mdx +188 -36
  182. package/docs/configuration/consent.mdx +100 -0
  183. package/docs/configuration/customization.mdx +6 -4
  184. package/docs/configuration/index.mdx +63 -5
  185. package/docs/configuration/meta.ts +3 -0
  186. package/docs/configuration/narration.mdx +119 -0
  187. package/docs/configuration/rate-limiting.mdx +69 -0
  188. package/docs/configuration/search.mdx +30 -2
  189. package/docs/configuration/theming.mdx +3 -1
  190. package/docs/content/components.mdx +107 -1
  191. package/docs/content/frontmatter.mdx +77 -1
  192. package/docs/content/i18n.mdx +40 -3
  193. package/docs/content/includes.mdx +16 -0
  194. package/docs/content/index.mdx +4 -2
  195. package/docs/content/islands.mdx +1 -1
  196. package/docs/content/meta.mdx +4 -1
  197. package/docs/content/meta.ts +1 -0
  198. package/docs/content/navigation.mdx +28 -6
  199. package/docs/content/sources/contentful.mdx +32 -0
  200. package/docs/content/sources/custom.mdx +39 -0
  201. package/docs/content/sources/github-releases.mdx +31 -0
  202. package/docs/content/sources/index.mdx +91 -0
  203. package/docs/content/sources/meta.ts +16 -0
  204. package/docs/content/sources/notion.mdx +32 -0
  205. package/docs/content/sources/obsidian.mdx +37 -0
  206. package/docs/content/sources/payload.mdx +30 -0
  207. package/docs/content/sources/remote-mdx.mdx +40 -0
  208. package/docs/content/sources/sanity.mdx +29 -0
  209. package/docs/content/sources/strapi.mdx +31 -0
  210. package/docs/content/syntax.mdx +81 -2
  211. package/docs/content/variables.mdx +41 -0
  212. package/docs/content/versioning.mdx +1 -1
  213. package/docs/discoverability/agent-discovery.mdx +30 -3
  214. package/docs/discoverability/index.mdx +3 -2
  215. package/docs/discoverability/llms-txt.mdx +1 -1
  216. package/docs/discoverability/markdown.mdx +2 -2
  217. package/docs/discoverability/metadata.mdx +17 -0
  218. package/docs/discoverability/open-graph.mdx +5 -3
  219. package/docs/discoverability/rss.mdx +1 -1
  220. package/docs/references/api-pages.mdx +74 -0
  221. package/docs/references/asyncapi.mdx +2 -2
  222. package/docs/references/graphql.mdx +3 -3
  223. package/docs/references/meta.ts +1 -1
  224. package/docs/references/openapi.mdx +106 -5
  225. package/docs/references/scalar.mdx +3 -1
  226. package/package.json +36 -1
  227. package/skills/blume/SKILL.md +3 -0
  228. package/skills/blume-migrate/SKILL.md +5 -5
  229. package/skills/blume-migrate/references/docusaurus.md +3 -3
  230. package/skills/blume-migrate/references/fumadocs.md +1 -1
  231. package/skills/blume-migrate/references/mintlify.md +37 -19
  232. package/skills/blume-migrate/references/nextra.md +3 -3
  233. package/skills/blume-migrate/references/starlight.md +2 -1
  234. package/skills/blume-migrate/scripts/mintlify-codemod.mjs +67 -16
  235. package/skills/blume-write-skill/SKILL.md +82 -0
  236. package/src/ai/agent-readability.ts +4 -3
  237. package/src/ai/agent-surface.ts +9 -7
  238. package/src/ai/ai-catalog.ts +12 -5
  239. package/src/ai/api/handlers.ts +2 -2
  240. package/src/ai/api/spec.ts +2 -2
  241. package/src/ai/api-catalog.ts +6 -2
  242. package/src/ai/ask-context.ts +73 -20
  243. package/src/ai/ask-data.ts +6 -0
  244. package/src/ai/ask-tools.ts +142 -0
  245. package/src/ai/ask.ts +354 -76
  246. package/src/ai/changelog-markdown.ts +2 -2
  247. package/src/ai/component-markdown.ts +75 -0
  248. package/src/ai/cors.ts +2 -0
  249. package/src/ai/index.ts +12 -0
  250. package/src/ai/link-headers.ts +14 -5
  251. package/src/ai/llms.ts +24 -9
  252. package/src/ai/markdown.ts +2 -1
  253. package/src/ai/mcp/data.ts +6 -0
  254. package/src/ai/mcp/discovery.ts +2 -2
  255. package/src/ai/mcp/query.ts +2 -2
  256. package/src/ai/mcp/server.ts +2 -2
  257. package/src/ai/openapi-components.ts +31 -9
  258. package/src/ai/relative-links.ts +42 -26
  259. package/src/ai/serializers.ts +2 -1
  260. package/src/ai/site-skill.ts +274 -0
  261. package/src/ai/skills.ts +49 -3
  262. package/src/ai/tar.ts +139 -12
  263. package/src/ai/write-skill.ts +97 -0
  264. package/src/analytics/head.ts +4 -0
  265. package/src/analytics/index.ts +5 -0
  266. package/src/analytics/one-dollar-stats.ts +86 -0
  267. package/src/analytics/schema.ts +2 -0
  268. package/src/astro/generate.ts +133 -19
  269. package/src/astro/include-hmr.ts +31 -12
  270. package/src/astro/include-refresh.ts +19 -8
  271. package/src/astro/index.ts +1 -0
  272. package/src/astro/integration.ts +143 -14
  273. package/src/astro/module-types.ts +6 -0
  274. package/src/astro/runtime-modules.ts +28 -13
  275. package/src/astro/templates.ts +364 -67
  276. package/src/astro/variables.ts +31 -0
  277. package/src/audit/catalog.ts +19 -0
  278. package/src/audit/checks/contrast.ts +183 -0
  279. package/src/audit/checks/i18n.ts +2 -3
  280. package/src/audit/checks/indexability.ts +2 -3
  281. package/src/audit/checks/links.ts +13 -13
  282. package/src/audit/checks/sitemap.ts +2 -3
  283. package/src/audit/graph.ts +3 -1
  284. package/src/audit/redirects.ts +46 -15
  285. package/src/audit/run.ts +14 -9
  286. package/src/audit/snapshot.ts +5 -0
  287. package/src/audit/types.ts +2 -1
  288. package/src/blume-modules.d.ts +7 -0
  289. package/src/captcha/client.ts +152 -0
  290. package/src/captcha/hcaptcha.ts +36 -0
  291. package/src/captcha/index.ts +28 -0
  292. package/src/captcha/schema.ts +32 -0
  293. package/src/captcha/turnstile.ts +38 -0
  294. package/src/captcha/verify.ts +71 -0
  295. package/src/cli/args.ts +32 -0
  296. package/src/cli/command-meta.ts +5 -0
  297. package/src/cli/commands/audit.ts +4 -1
  298. package/src/cli/commands/doctor.ts +25 -17
  299. package/src/cli/commands/eval.ts +19 -11
  300. package/src/cli/commands/skill.ts +96 -0
  301. package/src/cli/commands/translate.ts +9 -12
  302. package/src/cli/commands/validate.ts +3 -1
  303. package/src/cli/dev-lock.ts +157 -37
  304. package/src/cli/index.ts +5 -0
  305. package/src/cli/report-format.ts +11 -6
  306. package/src/cli/required-secrets.ts +71 -29
  307. package/src/components/BlumePage.astro +2 -0
  308. package/src/components/colors.ts +19 -0
  309. package/src/components/content/ApiEndpoint.astro +18 -0
  310. package/src/components/content/ApiField.astro +79 -0
  311. package/src/components/content/ApiPlayground.astro +62 -0
  312. package/src/components/content/ApiRail.astro +19 -0
  313. package/src/components/content/Badge.astro +5 -3
  314. package/src/components/content/Color.astro +1 -0
  315. package/src/components/content/Component.astro +2 -2
  316. package/src/components/content/FileTree.astro +1 -0
  317. package/src/components/content/GithubInfo.astro +1 -0
  318. package/src/components/content/Panel.astro +1 -0
  319. package/src/components/content/ParamField.astro +25 -0
  320. package/src/components/content/RequestExample.astro +17 -0
  321. package/src/components/content/ResponseExample.astro +17 -0
  322. package/src/components/content/ResponseField.astro +32 -0
  323. package/src/components/content/Step.astro +1 -0
  324. package/src/components/content/Tab.astro +0 -1
  325. package/src/components/content/Tabs.astro +4 -0
  326. package/src/components/content/Tile.astro +1 -0
  327. package/src/components/content/Tooltip.astro +1 -0
  328. package/src/components/content/Tree.astro +1 -0
  329. package/src/components/content/TypeTable.astro +4 -1
  330. package/src/components/content/View.astro +21 -0
  331. package/src/components/content/ViewSwitcher.astro +168 -0
  332. package/src/components/content/api-field.ts +100 -0
  333. package/src/components/content/api-page.ts +224 -0
  334. package/src/components/content/badge-color.ts +5 -2
  335. package/src/components/content/base-href.ts +13 -36
  336. package/src/components/content/view-switcher.ts +71 -0
  337. package/src/components/index.ts +2 -0
  338. package/src/components/islands/Assistant.astro +10 -1
  339. package/src/components/islands/assistant.tsx +125 -18
  340. package/src/components/islands/base-path.ts +47 -10
  341. package/src/components/islands/code-question.ts +62 -0
  342. package/src/components/islands/hooks.ts +145 -40
  343. package/src/components/islands/support-link.ts +68 -0
  344. package/src/components/islands/webmcp.ts +4 -2
  345. package/src/components/layout/Analytics.astro +11 -2
  346. package/src/components/layout/Banner.astro +14 -10
  347. package/src/components/layout/Breadcrumbs.astro +11 -4
  348. package/src/components/layout/ConsentBanner.astro +62 -0
  349. package/src/components/layout/ConsentHead.astro +38 -0
  350. package/src/components/layout/Directory.astro +94 -0
  351. package/src/components/layout/DiscoveryLinks.astro +9 -5
  352. package/src/components/layout/DrawerSwitchers.astro +51 -0
  353. package/src/components/layout/Empty.astro +2 -2
  354. package/src/components/layout/Header.astro +124 -82
  355. package/src/components/layout/LanguageSwitcher.astro +50 -17
  356. package/src/components/layout/NarrationPlayer.astro +1085 -0
  357. package/src/components/layout/NavSelector.astro +35 -9
  358. package/src/components/layout/NavTabMenu.astro +3 -3
  359. package/src/components/layout/NavTree.astro +16 -6
  360. package/src/components/layout/PageActions.astro +4 -3
  361. package/src/components/layout/PageFeedback.astro +54 -49
  362. package/src/components/layout/PageLayout.astro +78 -9
  363. package/src/components/layout/Pagination.astro +3 -3
  364. package/src/components/layout/ReferenceLayout.astro +17 -6
  365. package/src/components/layout/RelatedPages.astro +42 -0
  366. package/src/components/layout/RootLayout.astro +313 -50
  367. package/src/components/layout/Search.astro +64 -30
  368. package/src/components/layout/SiteFooter.astro +124 -0
  369. package/src/components/layout/SiteMetatags.astro +16 -0
  370. package/src/components/layout/VersionBanner.astro +2 -2
  371. package/src/components/layout/analytics-client.ts +12 -0
  372. package/src/components/layout/consent/native.ts +79 -0
  373. package/src/components/layout/consent/types.ts +37 -0
  374. package/src/components/layout/footer-links.ts +53 -0
  375. package/src/components/layout/footer-socials.ts +87 -0
  376. package/src/components/layout/locale-redirect.ts +107 -0
  377. package/src/components/layout/nav-utils.ts +83 -0
  378. package/src/components/layout/page-feedback.ts +96 -0
  379. package/src/components/layout/search/analytics.ts +98 -0
  380. package/src/components/layout/search/flexsearch.ts +17 -5
  381. package/src/components/layout/search/orama-cloud.ts +26 -14
  382. package/src/components/layout/search/types.ts +20 -0
  383. package/src/components/layout/search/typesense.ts +5 -1
  384. package/src/components/layout/toc-active.ts +41 -0
  385. package/src/components/layout/toc-element.ts +8 -14
  386. package/src/components/openapi/ApiTagOperations.astro +2 -2
  387. package/src/components/openapi/AsyncApiOperation.astro +7 -4
  388. package/src/components/openapi/GraphqlChip.astro +2 -2
  389. package/src/components/openapi/Operation.astro +163 -13
  390. package/src/components/openapi/Playground.astro +4 -4
  391. package/src/components/openapi/RequestBody.astro +6 -4
  392. package/src/components/openapi/RequestPanel.astro +63 -8
  393. package/src/components/openapi/Responses.astro +6 -4
  394. package/src/components/openapi/SchemaTable.astro +7 -0
  395. package/src/components/openapi/async-snippets.ts +4 -4
  396. package/src/components/openapi/async.ts +38 -6
  397. package/src/components/openapi/code-samples.ts +97 -0
  398. package/src/components/openapi/helpers.ts +19 -9
  399. package/src/components/openapi/message-composer.ts +6 -1
  400. package/src/components/openapi/message.ts +17 -2
  401. package/src/components/openapi/operation-model.ts +81 -11
  402. package/src/components/openapi/panel.ts +4 -2
  403. package/src/components/openapi/param-style.ts +181 -0
  404. package/src/components/openapi/playground-client.ts +73 -16
  405. package/src/components/openapi/request.ts +190 -26
  406. package/src/components/openapi/schema-tree.ts +30 -24
  407. package/src/components/openapi/security.ts +6 -3
  408. package/src/components/openapi/snippets.ts +953 -13
  409. package/src/components/openapi/ws-client.ts +18 -2
  410. package/src/components/props.ts +6 -0
  411. package/src/consent/client.ts +95 -0
  412. package/src/consent/clients.ts +19 -0
  413. package/src/consent/ethyca.ts +71 -0
  414. package/src/consent/head.ts +24 -0
  415. package/src/consent/index.ts +37 -0
  416. package/src/consent/init.ts +29 -0
  417. package/src/consent/native.ts +36 -0
  418. package/src/consent/osano.ts +59 -0
  419. package/src/consent/schema.ts +30 -0
  420. package/src/core/base-path.ts +62 -16
  421. package/src/core/builtin-tags.ts +5 -0
  422. package/src/core/client-address.ts +19 -0
  423. package/src/core/config-input.ts +203 -27
  424. package/src/core/data.ts +44 -4
  425. package/src/core/diagnostics.ts +5 -2
  426. package/src/core/directive-diagnostics.ts +99 -0
  427. package/src/core/footer.ts +26 -0
  428. package/src/core/frontmatter.ts +21 -18
  429. package/src/core/graph.ts +39 -36
  430. package/src/core/heading-markers.ts +5 -18
  431. package/src/core/i18n-ui.ts +82 -2
  432. package/src/core/i18n.ts +31 -3
  433. package/src/core/includes.ts +20 -3
  434. package/src/core/last-modified.ts +25 -3
  435. package/src/core/links.ts +16 -2
  436. package/src/core/locale-links.ts +5 -1
  437. package/src/core/localizable.ts +26 -0
  438. package/src/core/meta.ts +33 -19
  439. package/src/core/navigation.ts +71 -20
  440. package/src/core/page-modes.ts +56 -0
  441. package/src/core/project-graph.ts +138 -10
  442. package/src/core/redirect-patterns.ts +491 -0
  443. package/src/core/related.ts +131 -0
  444. package/src/core/schema.ts +342 -68
  445. package/src/core/sources/contentful-rich-text.ts +27 -20
  446. package/src/core/sources/filesystem.ts +20 -2
  447. package/src/core/sources/github-releases.ts +25 -6
  448. package/src/core/sources/lexical.ts +23 -18
  449. package/src/core/sources/lower.ts +201 -34
  450. package/src/core/sources/mdx-remote.ts +51 -16
  451. package/src/core/sources/normalize.ts +327 -91
  452. package/src/core/sources/notion.ts +52 -25
  453. package/src/core/sources/obsidian.ts +23 -6
  454. package/src/core/sources/portable-text.ts +38 -21
  455. package/src/core/sources/read.ts +8 -3
  456. package/src/core/sources/strapi-blocks.ts +20 -16
  457. package/src/core/sources/watch.ts +20 -7
  458. package/src/core/standard-schema.ts +10 -6
  459. package/src/core/types.ts +1 -1
  460. package/src/core/ui-packs/ar.ts +43 -0
  461. package/src/core/ui-packs/bg.ts +45 -0
  462. package/src/core/ui-packs/bn.ts +43 -0
  463. package/src/core/ui-packs/ca.ts +46 -0
  464. package/src/core/ui-packs/cs.ts +44 -0
  465. package/src/core/ui-packs/da.ts +45 -0
  466. package/src/core/ui-packs/de.ts +46 -0
  467. package/src/core/ui-packs/el.ts +45 -0
  468. package/src/core/ui-packs/es.ts +46 -0
  469. package/src/core/ui-packs/fa.ts +45 -0
  470. package/src/core/ui-packs/fi.ts +45 -0
  471. package/src/core/ui-packs/fr.ts +46 -0
  472. package/src/core/ui-packs/he.ts +43 -0
  473. package/src/core/ui-packs/hi.ts +44 -0
  474. package/src/core/ui-packs/hr.ts +45 -0
  475. package/src/core/ui-packs/hu.ts +44 -0
  476. package/src/core/ui-packs/id.ts +45 -0
  477. package/src/core/ui-packs/it.ts +45 -0
  478. package/src/core/ui-packs/ja.ts +45 -0
  479. package/src/core/ui-packs/ko.ts +44 -0
  480. package/src/core/ui-packs/nl.ts +46 -0
  481. package/src/core/ui-packs/no.ts +44 -0
  482. package/src/core/ui-packs/pl.ts +45 -0
  483. package/src/core/ui-packs/pt-br.ts +46 -0
  484. package/src/core/ui-packs/pt.ts +45 -0
  485. package/src/core/ui-packs/ro.ts +45 -0
  486. package/src/core/ui-packs/ru.ts +45 -0
  487. package/src/core/ui-packs/sk.ts +44 -0
  488. package/src/core/ui-packs/sr.ts +45 -0
  489. package/src/core/ui-packs/sv.ts +44 -0
  490. package/src/core/ui-packs/th.ts +43 -0
  491. package/src/core/ui-packs/tr.ts +45 -0
  492. package/src/core/ui-packs/uk.ts +45 -0
  493. package/src/core/ui-packs/vi.ts +45 -0
  494. package/src/core/ui-packs/zh-tw.ts +43 -0
  495. package/src/core/ui-packs/zh.ts +43 -0
  496. package/src/core/variables.ts +103 -0
  497. package/src/core/version-cut.ts +52 -15
  498. package/src/deploy/artifacts.ts +104 -14
  499. package/src/deploy/cloudflare-negotiation.ts +39 -17
  500. package/src/deploy/headers.ts +10 -5
  501. package/src/deploy/node-headers.ts +64 -10
  502. package/src/deploy/platforms/cloudflare.ts +44 -3
  503. package/src/deploy/platforms/netlify.ts +34 -1
  504. package/src/deploy/platforms/node.ts +1 -0
  505. package/src/deploy/platforms/static.ts +1 -0
  506. package/src/deploy/platforms/types.ts +8 -0
  507. package/src/deploy/platforms/vercel.ts +38 -2
  508. package/src/deploy/redirects.ts +139 -36
  509. package/src/deploy/robots.ts +2 -2
  510. package/src/deploy/rss.ts +4 -3
  511. package/src/deploy/sitemap.ts +7 -5
  512. package/src/deploy/vercel-negotiation.ts +36 -0
  513. package/src/markdown/api-rail.ts +259 -0
  514. package/src/markdown/base-links.ts +34 -36
  515. package/src/markdown/code-title.ts +35 -9
  516. package/src/markdown/directives.ts +242 -36
  517. package/src/markdown/features.ts +20 -2
  518. package/src/markdown/fence-meta.ts +2 -0
  519. package/src/markdown/include.ts +11 -1
  520. package/src/markdown/index.ts +35 -11
  521. package/src/markdown/math.ts +4 -4
  522. package/src/markdown/mdast.ts +5 -2
  523. package/src/markdown/relative-links.ts +12 -4
  524. package/src/markdown/variables.ts +56 -0
  525. package/src/markdown/views.ts +82 -0
  526. package/src/narration/build.ts +412 -0
  527. package/src/narration/player.ts +249 -0
  528. package/src/narration/provider.ts +29 -0
  529. package/src/narration/script.ts +592 -0
  530. package/src/og/card.ts +136 -11
  531. package/src/og/derive.ts +41 -31
  532. package/src/og/index.ts +1 -0
  533. package/src/openapi/asyncapi.ts +4 -2
  534. package/src/openapi/model.ts +77 -36
  535. package/src/openapi/overlay.ts +256 -0
  536. package/src/openapi/parse.ts +55 -2
  537. package/src/openapi/proxy.ts +63 -10
  538. package/src/openapi/references.ts +21 -5
  539. package/src/openapi/render-mdx.ts +46 -3
  540. package/src/openapi/scalar.ts +40 -6
  541. package/src/openapi/source.ts +1 -1
  542. package/src/ratelimit/cloudflare.ts +54 -0
  543. package/src/ratelimit/index.ts +30 -0
  544. package/src/ratelimit/memory.ts +45 -0
  545. package/src/ratelimit/runtime.ts +229 -0
  546. package/src/ratelimit/schema.ts +28 -0
  547. package/src/ratelimit/upstash.ts +30 -0
  548. package/src/ratelimit/wrangler.ts +70 -0
  549. package/src/reference/asyncapi.ts +2 -2
  550. package/src/reference/graphql.ts +5 -2
  551. package/src/reference/openapi.ts +21 -7
  552. package/src/reference/options.ts +51 -7
  553. package/src/reference/scalar.ts +33 -8
  554. package/src/registry/eject.ts +201 -28
  555. package/src/registry/registry.ts +7 -0
  556. package/src/search/adapters/version-scope.ts +30 -0
  557. package/src/search/documents.ts +93 -28
  558. package/src/search/orama-index.ts +28 -3
  559. package/src/search/pagefind-ranking.ts +39 -0
  560. package/src/search/popular.ts +2 -1
  561. package/src/search/sync/algolia.ts +15 -4
  562. package/src/search/sync/orama-cloud.ts +3 -0
  563. package/src/search/sync/typesense.ts +5 -0
  564. package/src/seo/jsonld.ts +7 -3
  565. package/src/seo/metatags.ts +67 -0
  566. package/src/theme/chrome-icons.ts +1 -0
  567. package/src/theme/color.ts +500 -0
  568. package/src/theme/entry.ts +56 -3
  569. package/src/theme/palette.ts +121 -45
  570. package/src/translate/meta.ts +68 -24
  571. package/src/translate/prompts.ts +1 -0
  572. package/src/translate/run.ts +3 -3
  573. package/src/translate/validate.ts +12 -3
  574. package/src/translate/work-list.ts +56 -17
  575. package/src/upgrade/upgrade.ts +4 -171
  576. package/dist/cli/chunk-3yce002v.js.map +0 -14
  577. package/dist/cli/chunk-5r8g91qn.js.map +0 -34
  578. package/dist/cli/chunk-6dtt0zfn.js.map +0 -10
  579. package/dist/cli/chunk-7mbqtmgb.js.map +0 -10
  580. package/dist/cli/chunk-ddndchfr.js.map +0 -14
  581. package/dist/cli/chunk-esh98wmb.js +0 -23
  582. package/dist/cli/chunk-esh98wmb.js.map +0 -10
  583. package/dist/cli/chunk-hqp2ajnh.js.map +0 -35
  584. package/dist/cli/chunk-hr8ne106.js.map +0 -13
  585. package/dist/cli/chunk-j85scx15.js.map +0 -10
  586. package/dist/cli/chunk-mqc662a6.js +0 -75
  587. package/dist/cli/chunk-mqc662a6.js.map +0 -11
  588. package/dist/cli/chunk-pbg5a4s3.js.map +0 -19
  589. package/dist/cli/chunk-qkb5a8sa.js.map +0 -10
  590. package/dist/cli/chunk-v6ya5kcb.js.map +0 -189
  591. package/dist/cli/chunk-wdrt2k2v.js.map +0 -10
  592. package/dist/cli/chunk-xh43dwgw.js.map +0 -36
  593. package/docs/content/sources.mdx +0 -338
  594. /package/dist/cli/{chunk-zp79m0ts.js.map → chunk-1n228xqb.js.map} +0 -0
  595. /package/dist/cli/{chunk-h2ez8dzb.js.map → chunk-5ffywsmb.js.map} +0 -0
  596. /package/dist/cli/{chunk-g698a744.js.map → chunk-5wj2qwn2.js.map} +0 -0
  597. /package/dist/cli/{chunk-sqw4ekg1.js.map → chunk-amrb6hgh.js.map} +0 -0
  598. /package/dist/cli/{chunk-h7k3nq3v.js.map → chunk-gp84za0h.js.map} +0 -0
  599. /package/dist/cli/{chunk-bfwp9vp6.js.map → chunk-p804f3pe.js.map} +0 -0
  600. /package/dist/cli/{chunk-00gs3wqs.js.map → chunk-ta27feev.js.map} +0 -0
  601. /package/dist/cli/{chunk-bbnwccaz.js.map → chunk-y4ykbark.js.map} +0 -0
@@ -1,5 +1,5 @@
1
1
  ---
2
- import { withBase } from "../islands/base-path.ts";
2
+ import { withMountedBase } from "../islands/base-path.ts";
3
3
  // A top-level navigation selector: a dropdown that switches
4
4
  // between partitions of the site — a product, a version, or any grouped set of
5
5
  // destinations (`navigation.selectors` in the config). Built on
@@ -19,9 +19,15 @@ interface Props {
19
19
  * overflow the viewport — pass `end` there.
20
20
  */
21
21
  align?: "start" | "end";
22
+ /**
23
+ * `bar` (the default) for the header; `drawer` for the top of the nav
24
+ * drawer, where the trigger spans the drawer, always shows its label, and
25
+ * opens a panel as wide as itself, so nothing reaches past the drawer edge.
26
+ */
27
+ variant?: "bar" | "drawer";
22
28
  }
23
29
 
24
- const { selector, route, align = "start" } = Astro.props;
30
+ const { selector, route, align = "start", variant = "bar" } = Astro.props;
25
31
 
26
32
  // The active item is the deepest path the current route sits under (on a path
27
33
  // boundary, so `/api` never claims `/api-reference` routes), falling back to
@@ -37,25 +43,37 @@ const active =
37
43
  // version like "v2" or "Latest"): on a phone a bare chevron doesn't say what
38
44
  // it switches. Longer product names still collapse to the icon there.
39
45
  const label = active?.label ?? selector.label;
40
- const labelClass = label.length <= 6 ? undefined : "max-sm:hidden";
46
+ let labelClass: string | undefined;
47
+ if (variant === "drawer") {
48
+ labelClass = "flex-1 text-start";
49
+ } else if (label.length > 6) {
50
+ labelClass = "max-sm:hidden";
51
+ }
41
52
  // The accessible name carries the visible active label too (WCAG 2.5.3,
42
53
  // label in name), so a voice-control user can say "v2" to open it — the
43
54
  // language switcher's pattern.
44
55
  const summaryLabel =
45
56
  label === selector.label ? selector.label : `${selector.label}: ${label}`;
46
57
 
47
- const iconButton =
48
- "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";
58
+ // Borderless in the header, like the icon buttons and the language switcher;
59
+ // bordered in the drawer, where the trigger spans it.
60
+ const triggerClass =
61
+ variant === "bar"
62
+ ? "inline-flex h-9 cursor-pointer items-center gap-1.5 rounded-full px-3 text-muted-foreground text-sm transition-colors hover:bg-muted hover:text-foreground group-open:bg-muted group-open:text-foreground"
63
+ : "inline-flex h-9 w-full 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";
49
64
  const menuRowClass =
50
65
  "flex w-full items-start gap-2.5 rounded-md px-2 py-1.5 text-start text-muted-foreground text-sm transition-colors hover:bg-muted hover:text-foreground aria-[current=true]:text-foreground";
51
66
  ---
52
67
 
53
68
  {
54
69
  selector.items.length > 0 && (
55
- <details class="group relative" data-blume-dropdown>
70
+ <details
71
+ class:list={["group relative", variant === "drawer" && "w-full"]}
72
+ data-blume-dropdown
73
+ >
56
74
  <summary
57
75
  aria-label={summaryLabel}
58
- class={`${iconButton} list-none [&::-webkit-details-marker]:hidden`}
76
+ class:list={[triggerClass, "list-none [&::-webkit-details-marker]:hidden"]}
59
77
  >
60
78
  {active?.icon && <Icon name={active.icon} size={16} />}
61
79
  <span class={labelClass}>{label}</span>
@@ -66,14 +84,22 @@ const menuRowClass =
66
84
  />
67
85
  </summary>
68
86
  <div
69
- 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"}`}
87
+ class:list={[
88
+ "absolute z-50 mt-2 rounded-blume border border-border bg-background p-1 shadow-xl",
89
+ variant === "drawer"
90
+ ? "inset-x-0"
91
+ : [
92
+ "min-w-56 max-w-[calc(100vw-1rem)]",
93
+ align === "end" ? "end-0" : "start-0",
94
+ ],
95
+ ]}
70
96
  data-blume-dropdown-panel
71
97
  >
72
98
  {selector.items.map((item) => (
73
99
  <a
74
100
  aria-current={item.path === active?.path ? "true" : undefined}
75
101
  class={menuRowClass}
76
- href={withBase(item.path)}
102
+ href={withMountedBase(item.path)}
77
103
  >
78
104
  {item.icon && <Icon class="mt-0.5" name={item.icon} size={16} />}
79
105
  <span class="flex-1">
@@ -10,7 +10,7 @@
10
10
  // delegate from `document`, so client-router swaps need no re-init.
11
11
  import type { NavTab } from "../../core/types.ts";
12
12
  import Icon from "../Icon.astro";
13
- import { withBase } from "../islands/base-path.ts";
13
+ import { withMountedBase } from "../islands/base-path.ts";
14
14
 
15
15
  interface Props {
16
16
  tab: NavTab;
@@ -60,7 +60,7 @@ const drawerRowClass =
60
60
  <a
61
61
  aria-current={itemCurrent(item.path)}
62
62
  class={barRowClass}
63
- href={withBase(item.path)}
63
+ href={withMountedBase(item.path)}
64
64
  >
65
65
  {item.icon && <Icon class="mt-0.5" name={item.icon} size={16} />}
66
66
  <span class="flex-1">
@@ -102,7 +102,7 @@ const drawerRowClass =
102
102
  <a
103
103
  aria-current={itemCurrent(item.path)}
104
104
  class={drawerRowClass}
105
- href={withBase(item.path)}
105
+ href={withMountedBase(item.path)}
106
106
  >
107
107
  {item.icon && (
108
108
  <Icon class="shrink-0" name={item.icon} size={14} />
@@ -4,7 +4,7 @@ import type { UIStrings } from "../../core/i18n-ui.ts";
4
4
  import type { NavNode } from "../../core/types.ts";
5
5
  import { methodColor } from "../colors.ts";
6
6
  import Icon from "../Icon.astro";
7
- import { withBase } from "../islands/base-path.ts";
7
+ import { withBase, withMountedBase } 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";
@@ -82,6 +82,16 @@ const n = { ...EN_UI.nav, ...strings };
82
82
  /** The DOM id of a drill-in panel, which its drill button controls. */
83
83
  const navPanelId = (id: string): string => `blume-nav-panel-${id}`;
84
84
 
85
+ /**
86
+ * A page row's href. A row backed by a page links a route Blume generates, so
87
+ * the deployment base always goes on — even over a route that starts with the
88
+ * base's own name. A config `href` link (no backing page) is authored, and
89
+ * keeps a base written by hand. Group rows link their index page the same way
90
+ * a backed page row does.
91
+ */
92
+ const pageHref = (item: Extract<NavNode, { kind: "page" }>): string =>
93
+ item.pageId ? withMountedBase(item.route) : withBase(item.route);
94
+
85
95
  const badgeBase =
86
96
  "shrink-0 rounded-full px-1.5 py-0.5 font-medium text-[0.65rem] leading-none";
87
97
  // HTTP-method, AsyncAPI-action, and GraphQL operation-kind badges (from a
@@ -191,7 +201,7 @@ const initialId =
191
201
  : undefined
192
202
  }
193
203
  class="flex-1 truncate rounded-[0.65rem] px-1 py-1 font-semibold text-foreground text-sm transition-colors hover:bg-muted"
194
- href={withBase(panel.route)}
204
+ href={withMountedBase(panel.route)}
195
205
  >
196
206
  {panel.label}
197
207
  </a>
@@ -257,7 +267,7 @@ const initialId =
257
267
  <a
258
268
  aria-current={current}
259
269
  class="blume-nav-link truncate"
260
- href={withBase(item.route)}
270
+ href={pageHref(item)}
261
271
  >
262
272
  {item.label}
263
273
  </a>
@@ -269,7 +279,7 @@ const initialId =
269
279
  <a
270
280
  aria-current={current}
271
281
  class="blume-nav-link"
272
- href={withBase(item.route)}
282
+ href={pageHref(item)}
273
283
  >
274
284
  <span class="flex items-center gap-2">
275
285
  {item.icon && (
@@ -348,7 +358,7 @@ const initialId =
348
358
  : undefined
349
359
  }
350
360
  class="blume-nav-summary-link"
351
- href={withBase(item.route)}
361
+ href={withMountedBase(item.route)}
352
362
  >
353
363
  {item.icon && (
354
364
  <Icon
@@ -432,7 +442,7 @@ const initialId =
432
442
  isGroupRowCurrent(item, currentRoute) ? "page" : undefined
433
443
  }
434
444
  class="blume-nav-heading-link"
435
- href={withBase(item.route)}
445
+ href={withMountedBase(item.route)}
436
446
  >
437
447
  {item.icon && (
438
448
  <Icon
@@ -262,7 +262,7 @@ const menuRowClass =
262
262
  <script>
263
263
  import { copyDeferredText, copyText, flashLabel } from "../copy-feedback.ts";
264
264
  import { installDropdownDismiss } from "../dropdown-dismiss.ts";
265
- import { prefixBase } from "../islands/base-path.ts";
265
+ import { mountBase } from "../islands/base-path.ts";
266
266
  import { rafThrottle } from "../raf-throttle.ts";
267
267
 
268
268
  const CHAT_URLS: Record<string, (q: string) => string> = {
@@ -390,9 +390,10 @@ hr { border: 0; border-top: 1px solid #ddd; margin: 2em 0; }`;
390
390
 
391
391
  // `data-md` is the base-less logical route; the raw-markdown endpoint is a
392
392
  // generated page route, so it's served under the deployment base like any
393
- // other page. Prefix at emit time (the Search-island pattern).
393
+ // other page, even when it starts with the base's own name. Mount it at
394
+ // emit time (the Search-island pattern).
394
395
  const mdRoute = root.getAttribute("data-md") ?? "";
395
- const md = prefixBase(import.meta.env.BASE_URL, mdRoute);
396
+ const md = mountBase(import.meta.env.BASE_URL, mdRoute);
396
397
  const copiedLabel = root.getAttribute("data-i18n-copied") || "Copied!";
397
398
  const copyFailedLabel =
398
399
  root.getAttribute("data-i18n-copy-failed") || "Copy failed";
@@ -1,14 +1,22 @@
1
1
  ---
2
+ import data from "blume:data";
3
+
2
4
  import { EN_UI } from "../../core/i18n-ui.ts";
3
5
  import type { UIStrings } from "../../core/i18n-ui.ts";
4
6
  import Icon from "../Icon.astro";
7
+ import { COMMENT_MAX_LENGTH } from "./page-feedback.ts";
5
8
 
6
9
  interface Props {
10
+ /** Ask for a written comment after the rating (`feedback.comments`). */
11
+ comments?: boolean;
7
12
  /** Localized feedback labels. */
8
13
  strings?: UIStrings["feedback"];
9
14
  }
10
15
 
11
- const { strings } = Astro.props;
16
+ const { comments = false, strings } = Astro.props;
17
+ // Answers go through analytics, so under a consent layer the rating waits,
18
+ // hidden, until the reader allows analytics (`page-feedback.ts` shows it).
19
+ const waitsForConsent = data.config.consent !== null;
12
20
  // Merge over the English baseline per key (rather than `strings ?? …`) so a
13
21
  // partial — or empty `{}` — strings object still resolves every label to a
14
22
  // default, matching the pattern PageActions and Search use.
@@ -20,61 +28,58 @@ const buttonClass =
20
28
 
21
29
  <section
22
30
  aria-label={f.question}
23
- class="mx-auto mt-12 flex max-w-content items-center justify-between gap-4 border-border border-t pt-6"
31
+ class="mx-auto mt-12 max-w-content border-border border-t pt-6"
24
32
  data-blume-page-feedback
33
+ hidden={waitsForConsent}
25
34
  >
26
- <p class="text-muted-foreground text-sm">{f.question}</p>
27
- <div class="flex items-center gap-2" data-feedback-actions>
28
- <button class={buttonClass} data-feedback-value="yes" type="button">
29
- <Icon name="thumbs-up" size={16} />
30
- {f.yes}
31
- </button>
32
- <button class={buttonClass} data-feedback-value="no" type="button">
33
- <Icon name="thumbs-down" size={16} />
34
- {f.no}
35
- </button>
35
+ <div class="flex items-center justify-between gap-4">
36
+ <p class="text-muted-foreground text-sm">{f.question}</p>
37
+ <div class="flex items-center gap-2" data-feedback-actions>
38
+ <button class={buttonClass} data-feedback-value="yes" type="button">
39
+ <Icon name="thumbs-up" size={16} />
40
+ {f.yes}
41
+ </button>
42
+ <button class={buttonClass} data-feedback-value="no" type="button">
43
+ <Icon name="thumbs-down" size={16} />
44
+ {f.no}
45
+ </button>
46
+ </div>
47
+ <p
48
+ class="hidden text-muted-foreground text-sm focus:outline-none"
49
+ data-feedback-thanks
50
+ role="status"
51
+ tabindex="-1"
52
+ >
53
+ {f.thanks}
54
+ </p>
36
55
  </div>
37
- <p
38
- class="hidden text-muted-foreground text-sm focus:outline-none"
39
- data-feedback-thanks
40
- role="status"
41
- tabindex="-1"
42
- >
43
- {f.thanks}
44
- </p>
56
+ {
57
+ comments && (
58
+ <form class="mt-4 grid gap-2" data-feedback-form hidden>
59
+ <label class="text-muted-foreground text-sm" for="blume-feedback-comment">
60
+ {f.comment}
61
+ </label>
62
+ <textarea
63
+ class="w-full resize-y rounded-blume border border-border bg-background px-3 py-2 text-foreground text-sm outline-none transition-colors focus:border-foreground"
64
+ id="blume-feedback-comment"
65
+ maxlength={COMMENT_MAX_LENGTH}
66
+ name="comment"
67
+ rows="3"
68
+ ></textarea>
69
+ <div>
70
+ <button class={buttonClass} type="submit">
71
+ {f.send}
72
+ </button>
73
+ </div>
74
+ </form>
75
+ )
76
+ }
45
77
  </section>
46
78
 
47
79
  <script>
48
- import { track } from "./analytics-client.ts";
49
-
50
- // Run on the initial load and again after every client-router swap, which
51
- // rebuilds the rating widget from server-rendered markup — the handlers
52
- // below always bind to the freshly swapped-in elements.
53
- const initFeedback = () => {
54
- const root = document.querySelector("[data-blume-page-feedback]");
55
- if (!root) {
56
- return;
57
- }
58
- const actions = root.querySelector("[data-feedback-actions]");
59
- const thanks = root.querySelector("[data-feedback-thanks]");
60
- for (const button of root.querySelectorAll("[data-feedback-value]")) {
61
- button.addEventListener("click", () => {
62
- track("feedback", {
63
- helpful: button.getAttribute("data-feedback-value") ?? "",
64
- path: location.pathname,
65
- title: document.title,
66
- });
67
- actions?.classList.add("hidden");
68
- thanks?.classList.remove("hidden");
69
- // The clicked button just disappeared under focus; hand focus to the
70
- // (live) thanks line so the reader isn't dropped at the top of the page.
71
- if (thanks instanceof HTMLElement) {
72
- thanks.focus();
73
- }
74
- });
75
- }
76
- };
80
+ import { initFeedback, syncFeedbackVisibility } from "./page-feedback.ts";
77
81
 
78
82
  initFeedback();
79
83
  document.addEventListener("astro:after-swap", initFeedback);
84
+ addEventListener("blume:consent", syncFeedbackVisibility);
80
85
  </script>
@@ -5,8 +5,8 @@ import { createIconSprite } from "../icon-sprite.ts";
5
5
  // page that wants Blume's chrome (document shell, header, theme, fonts) without
6
6
  // the docs sidebar + prose + TOC grid that RootLayout hard-codes. It renders the
7
7
  // `<head>`, theme/banner pre-paint scripts, banner, and header, then a single
8
- // full-width `<slot />` for the body (plus an optional `footer` slot rendered
9
- // after `<main>`). Import it into a custom page and pull props from `blume:data`:
8
+ // full-width `<slot />` for the body, then an optional `footer` slot, or the
9
+ // site footer `footer` configures when the page fills no slot. Import it into a custom page and pull props from `blume:data`:
10
10
  //
11
11
  // import PageLayout from "blume/components/layout/PageLayout.astro";
12
12
  // import data from "blume:data";
@@ -39,12 +39,19 @@ import {
39
39
  import { buildStructuredData } from "../../seo/jsonld.ts";
40
40
  import { normalizeXHandle } from "../../seo/x-handle.ts";
41
41
  import Icon from "../Icon.astro";
42
- import { stripBase, withBase } from "../islands/base-path.ts";
42
+ import { stripBase, withBase, withMountedBase } from "../islands/base-path.ts";
43
+ import { routeSetFor } from "../../core/locale-links.ts";
44
+ import {
45
+ browserLanguageTargets,
46
+ LOCALE_REDIRECT_SCRIPT,
47
+ } from "./locale-redirect.ts";
43
48
  import { pageDirection, pageLocale } from "./page-locale.ts";
44
49
  import { searchLocaleFor } from "./search-locale.ts";
45
50
  import "blume:theme";
46
51
  import { ClientRouter } from "astro:transitions";
47
52
  import Analytics from "./Analytics.astro";
53
+ import ConsentBanner from "./ConsentBanner.astro";
54
+ import ConsentHead from "./ConsentHead.astro";
48
55
  import Banner from "./Banner.astro";
49
56
  import DiscoveryLinks from "./DiscoveryLinks.astro";
50
57
  import Favicon from "./Favicon.astro";
@@ -57,6 +64,9 @@ import {
57
64
  import Header from "./Header.astro";
58
65
  import { currentTabForRoute } from "./nav-utils.ts";
59
66
  import NavTabMenu from "./NavTabMenu.astro";
67
+ import DrawerSwitchers from "./DrawerSwitchers.astro";
68
+ import SiteFooter from "./SiteFooter.astro";
69
+ import SiteMetatags from "./SiteMetatags.astro";
60
70
  import WebMcp from "./WebMcp.astro";
61
71
 
62
72
  interface Props {
@@ -195,6 +205,8 @@ const clientDataJson = clientData
195
205
  : null;
196
206
 
197
207
  const strings = ui ?? EN_UI;
208
+ // The consent adapter (`consent`), site-wide: analytics waits for the reader.
209
+ const consent = data.config.consent;
198
210
  // Merge over the English defaults so a label missing from a translation (or
199
211
  // from a not-yet-regenerated snapshot) still renders instead of coming out
200
212
  // blank — the PageActions pattern.
@@ -219,7 +231,7 @@ const activeTab = currentTabForRoute(navigation.tabs, route, navigation.root);
219
231
  // llms.txt index), and the homepage HTTP `Link` header already advertises it,
220
232
  // so the head is its HTML counterpart on hosts that can't set that header.
221
233
  // Advertising a mirror for any other custom route would 404.
222
- const markdownMirror = route === "/" ? withBase("/index.md") : null;
234
+ const markdownMirror = route === "/" ? withMountedBase("/index.md") : null;
223
235
 
224
236
  // Derive canonical + og:image from the site URL the same way the catch-all does
225
237
  // for content pages, so a custom page gets both for free. The matching OG card
@@ -230,8 +242,9 @@ const markdownMirror = route === "/" ? withBase("/index.md") : null;
230
242
  const ogSlug = route === "/" ? "index" : route.slice(1);
231
243
  const ogCardGenerated = !Astro.routePattern.includes("[");
232
244
  // Absolute URLs carry the deployment base too (`site + base + route`), since the
233
- // page is served under it. `withBase` handles a root base as a no-op.
234
- const basedRoute = withBase(route);
245
+ // page is served under it — even when the route starts with the base's own
246
+ // name. `withMountedBase` handles a root base as a no-op.
247
+ const basedRoute = withMountedBase(route);
235
248
  // The schema accepts a trailing-slash site URL (`https://docs.example.com/`);
236
249
  // strip it before joining with the root-relative route so canonical/og URLs
237
250
  // don't come out double-slashed — the catch-all strips it the same way.
@@ -253,7 +266,7 @@ const absolutizeOgImage = (value: string): string =>
253
266
  const resolvedOgImage = ogImage
254
267
  ? absolutizeOgImage(ogImage)
255
268
  : ogEnabled && ogCardGenerated && siteBase
256
- ? `${siteBase}${encodeURI(withBase(`/og/${ogSlug}.png`))}`
269
+ ? `${siteBase}${encodeURI(withMountedBase(`/og/${ogSlug}.png`))}`
257
270
  : null;
258
271
  // Only the generated card has a known size and format, so its dimensions are
259
272
  // declared for crawlers; a user-supplied `ogImage` could be any image.
@@ -290,6 +303,25 @@ const bannerKey = banner?.dismissible ? banner.key : null;
290
303
  // Start the page's icon sprite before anything renders an icon (see
291
304
  // icon-sprite.ts); <IconSprite /> at the end of the body emits it.
292
305
  createIconSprite(Astro.locals);
306
+
307
+ // A visitor landing on the default language's home page goes to their
308
+ // browser's language (`i18n.routeByBrowserLanguage`, see locale-redirect.ts).
309
+ const localeTargets = browserLanguageTargets({
310
+ basePath: data.config.basePath,
311
+ i18n: data.config.i18n,
312
+ route,
313
+ routes: routeSetFor(data.routes),
314
+ });
315
+ const localeRedirect = localeTargets
316
+ ? JSON.stringify(
317
+ Object.fromEntries(
318
+ Object.entries(localeTargets).map(([code, path]) => [
319
+ code,
320
+ withMountedBase(path),
321
+ ])
322
+ )
323
+ )
324
+ : null;
293
325
  ---
294
326
 
295
327
  <!doctype html>
@@ -297,6 +329,16 @@ createIconSprite(Astro.locals);
297
329
  <head>
298
330
  <meta charset="utf-8" />
299
331
  <meta name="viewport" content="width=device-width, initial-scale=1" />
332
+ {
333
+ localeRedirect && (
334
+ <script
335
+ data-default={data.config.i18n?.defaultLocale}
336
+ data-targets={localeRedirect}
337
+ is:inline
338
+ set:html={LOCALE_REDIRECT_SCRIPT}
339
+ />
340
+ )
341
+ }
300
342
  {/* Client-side navigation, matching RootLayout: same-origin link clicks
301
343
  swap the DOM in place instead of tearing the document down, so no
302
344
  browser paints a blank frame between pages. */}
@@ -362,6 +404,7 @@ createIconSprite(Astro.locals);
362
404
  {description && <meta content={description} name="twitter:description" />}
363
405
  {xSite && <meta content={xSite} name="twitter:site" />}
364
406
  {xCreator && <meta content={xCreator} name="twitter:creator" />}
407
+ <SiteMetatags tags={data.config.metatags} />
365
408
  {/* Agent discovery on every page, so a custom page built on PageLayout is
366
409
  covered without passing a prop, the same way the docs pages are. */}
367
410
  <DiscoveryLinks discovery={discovery} markdownMirror={markdownMirror} />
@@ -380,6 +423,7 @@ createIconSprite(Astro.locals);
380
423
  )
381
424
  }
382
425
  <script data-mode={themeMode} is:inline set:html={THEME_INIT_SCRIPT} />
426
+ {consent && <ConsentHead consent={consent} />}
383
427
  <Analytics analytics={analytics} />
384
428
  </head>
385
429
  <body class="bg-background font-sans text-foreground antialiased">
@@ -408,8 +452,27 @@ createIconSprite(Astro.locals);
408
452
  transparent={transparentHeader}
409
453
  />
410
454
  <main id="blume-content"><slot /></main>
411
- <slot name="footer" />
455
+ {
456
+ Astro.slots.has("footer") ? (
457
+ <slot name="footer" />
458
+ ) : (
459
+ <SiteFooter
460
+ footer={data.config.footer}
461
+ locale={locale}
462
+ navigation={navigation}
463
+ ui={strings}
464
+ />
465
+ )
466
+ }
412
467
  <WebMcp />
468
+ {
469
+ consent?.kind === "native" && (
470
+ <ConsentBanner
471
+ policy={consent.options.policy}
472
+ strings={strings.consent}
473
+ />
474
+ )
475
+ }
413
476
  {
414
477
  // Mobile nav for chrome-only pages: the header's inline tab bar is hidden
415
478
  // below `lg` and there's no sidebar drawer, so surface the tabs in a
@@ -424,6 +487,12 @@ createIconSprite(Astro.locals);
424
487
  data-blume-nav-drawer
425
488
  id="blume-nav-drawer"
426
489
  >
490
+ <DrawerSwitchers
491
+ localeSwitch={localeSwitch}
492
+ route={route}
493
+ selectors={navigation.selectors}
494
+ switcherStrings={strings.languageSwitcher}
495
+ />
427
496
  <nav aria-label={navStrings.sections}>
428
497
  <ul class="m-0 list-none p-0">
429
498
  {navigation.tabs.map((tab) => (
@@ -439,7 +508,7 @@ createIconSprite(Astro.locals);
439
508
  <a
440
509
  aria-current={tab === activeTab ? "page" : undefined}
441
510
  class="flex items-center gap-2 rounded-[0.65rem] px-2.5 py-1.5 font-medium text-muted-foreground text-sm transition-colors hover:bg-muted hover:text-foreground aria-[current=page]:bg-muted aria-[current=page]:text-foreground"
442
- href={withBase(tab.href ?? tab.path)}
511
+ href={withMountedBase(tab.href ?? tab.path)}
443
512
  >
444
513
  {tab.icon && (
445
514
  <Icon class="shrink-0" name={tab.icon} size={14} />
@@ -1,5 +1,5 @@
1
1
  ---
2
- import { withBase } from "../islands/base-path.ts";
2
+ import { withMountedBase } from "../islands/base-path.ts";
3
3
  import { EN_UI } from "../../core/i18n-ui.ts";
4
4
  import type { UIStrings } from "../../core/i18n-ui.ts";
5
5
  import type { FlatPage } from "./nav-utils.ts";
@@ -31,7 +31,7 @@ const s = { ...EN_UI.page, ...strings };
31
31
  {prev ? (
32
32
  <a
33
33
  class="flex max-w-[48%] flex-1 items-center gap-2 rounded-blume border border-border px-4 py-3 text-foreground transition-colors hover:border-foreground"
34
- href={withBase(prev.route)}
34
+ href={withMountedBase(prev.route)}
35
35
  >
36
36
  <Icon class="rtl:-scale-x-100" name="arrow-left" size={16} />
37
37
  <span class="min-w-0">
@@ -47,7 +47,7 @@ const s = { ...EN_UI.page, ...strings };
47
47
  {next && (
48
48
  <a
49
49
  class="ms-auto flex max-w-[48%] flex-1 items-center justify-end gap-2 rounded-blume border border-border px-4 py-3 text-end text-foreground transition-colors hover:border-foreground"
50
- href={withBase(next.route)}
50
+ href={withMountedBase(next.route)}
51
51
  >
52
52
  <span class="min-w-0">
53
53
  <span class="block text-muted-foreground text-xs">
@@ -4,6 +4,7 @@ import { createIconSprite } from "../icon-sprite.ts";
4
4
  import "blume:theme";
5
5
  import data from "blume:data";
6
6
  import type {
7
+ BlumeBanner,
7
8
  BlumeDataConfig,
8
9
  BlumeFavicon,
9
10
  DiscoveryHeadLinks,
@@ -14,6 +15,8 @@ import { NEW_TAB_HINT_ID } from "../../core/new-tab.ts";
14
15
  import type { FontHead } from "../../theme/fonts.ts";
15
16
  import type { Navigation } from "../../core/types.ts";
16
17
  import Analytics from "./Analytics.astro";
18
+ import ConsentBanner from "./ConsentBanner.astro";
19
+ import ConsentHead from "./ConsentHead.astro";
17
20
  import WebMcp from "./WebMcp.astro";
18
21
  import Banner from "./Banner.astro";
19
22
  import DiscoveryLinks from "./DiscoveryLinks.astro";
@@ -27,6 +30,7 @@ import {
27
30
  import Header from "./Header.astro";
28
31
  import { pageDirection, pageLocale } from "./page-locale.ts";
29
32
  import { searchLocaleFor } from "./search-locale.ts";
33
+ import SiteMetatags from "./SiteMetatags.astro";
30
34
 
31
35
  // A minimal shell for the Scalar API/AsyncAPI reference: Blume's banner + navbar
32
36
  // on top, then a full-height region the reference mounts into. Unlike RootLayout
@@ -50,12 +54,7 @@ interface Props {
50
54
  } | null;
51
55
  favicon?: BlumeFavicon | null;
52
56
  appleIcon?: BlumeFavicon | null;
53
- banner?: {
54
- content: string;
55
- link?: { text: string; href: string };
56
- dismissible: boolean;
57
- key: string;
58
- } | null;
57
+ banner?: BlumeBanner | null;
59
58
  analytics?: BlumeDataConfig["analytics"] | null;
60
59
  navigation: Navigation;
61
60
  route: string;
@@ -110,6 +109,8 @@ const locale = pageLocale(data.config.i18n, localeProp, Astro.currentLocale);
110
109
  const dir = dirProp ?? pageDirection(data.config.i18n, locale);
111
110
 
112
111
  const strings = ui ?? EN_UI;
112
+ // The consent adapter (`consent`), site-wide: analytics waits for the reader.
113
+ const consent = data.config.consent;
113
114
  const navStrings = { ...EN_UI.nav, ...strings.nav };
114
115
  // Scope search to the reference page's language on a multi-locale site.
115
116
  const searchLocale = searchLocaleFor(data.config.i18n, locale);
@@ -128,6 +129,7 @@ createIconSprite(Astro.locals);
128
129
  <meta name="viewport" content="width=device-width, initial-scale=1" />
129
130
  {noindex && <meta name="robots" content="noindex" />}
130
131
  <title>{pageTitle}</title>
132
+ <SiteMetatags tags={data.config.metatags} />
131
133
  <Favicon favicon={favicon} appleIcon={appleIcon} />
132
134
  <Fonts cssVars={fontCssVars ?? []} />
133
135
  <DiscoveryLinks discovery={discovery} />
@@ -137,6 +139,7 @@ createIconSprite(Astro.locals);
137
139
  )
138
140
  }
139
141
  <script data-mode={themeMode} is:inline set:html={THEME_INIT_SCRIPT} />
142
+ {consent && <ConsentHead consent={consent} />}
140
143
  <Analytics analytics={analytics} />
141
144
  </head>
142
145
  <body class="bg-background font-sans text-foreground antialiased">
@@ -170,6 +173,14 @@ createIconSprite(Astro.locals);
170
173
  <script is:inline set:html={SCALAR_THEME_INIT_SCRIPT} />
171
174
  </div>
172
175
  <WebMcp />
176
+ {
177
+ consent?.kind === "native" && (
178
+ <ConsentBanner
179
+ policy={consent.options.policy}
180
+ strings={strings.consent}
181
+ />
182
+ )
183
+ }
173
184
  <IconSprite />
174
185
  </body>
175
186
  </html>