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
@@ -15,14 +15,15 @@ import {
15
15
  pollingWatch,
16
16
  snapshotCache,
17
17
  } from "./cache.ts";
18
+ import type { InlineRun } from "./lower.ts";
18
19
  import {
19
20
  codeFence,
20
21
  guardBlockStart,
21
22
  image,
23
+ linkParts,
22
24
  renderInline,
23
- renderLink,
24
25
  } from "./lower.ts";
25
- import { slugifyPath } from "./normalize.ts";
26
+ import { slugify, slugifyPath } from "./normalize.ts";
26
27
  import type {
27
28
  ContentSource,
28
29
  SourceContext,
@@ -160,26 +161,43 @@ interface NotionFrontmatter {
160
161
  [key: string]: boolean | string | { order: number } | undefined;
161
162
  }
162
163
 
164
+ /** Neighboring rich-text runs that link to one `href` (or none), and it. */
165
+ interface LinkedRuns {
166
+ href?: string;
167
+ runs: InlineRun[];
168
+ }
169
+
163
170
  /**
164
- * Rich text as Markdown for an MDX body. Each run goes through the shared
171
+ * Rich text as Markdown for an MDX body. The runs go through the shared
165
172
  * lowering, so a literal `{`, `<`, `*`, or `[` typed in Notion renders as
166
- * itself instead of opening a JSX expression, a tag, or emphasis, and a code
167
- * run keeps its text verbatim inside a long-enough code span.
173
+ * itself instead of opening a JSX expression, a tag, or emphasis, a code run
174
+ * keeps its text verbatim inside a long-enough code span, neighbors share
175
+ * their marks' delimiters, and neighbors that link to one `href` share one
176
+ * link.
168
177
  */
169
- const richToMarkdown = (rich: NotionRichText[] = []): string =>
170
- rich
171
- .map(({ annotations = {}, href, plain_text: text }) =>
172
- renderLink(
173
- renderInline(text, {
174
- bold: annotations.bold,
175
- code: annotations.code,
176
- italic: annotations.italic,
177
- strike: annotations.strikethrough,
178
- }),
179
- href ?? undefined
180
- )
181
- )
182
- .join("");
178
+ const richToMarkdown = (rich: NotionRichText[] = []): string => {
179
+ const linked: LinkedRuns[] = [];
180
+ for (const { annotations = {}, href, plain_text: text } of rich) {
181
+ const run: InlineRun = {
182
+ marks: {
183
+ bold: annotations.bold,
184
+ code: annotations.code,
185
+ italic: annotations.italic,
186
+ strike: annotations.strikethrough,
187
+ },
188
+ text,
189
+ };
190
+ const last = linked.at(-1);
191
+ if (last && last.href === (href ?? undefined)) {
192
+ last.runs.push(run);
193
+ } else {
194
+ linked.push({ href: href ?? undefined, runs: [run] });
195
+ }
196
+ }
197
+ return renderInline(
198
+ linked.flatMap(({ href, runs }) => linkParts(runs, href))
199
+ );
200
+ };
183
201
 
184
202
  const isBlockPayload = (
185
203
  value: NotionBlockPayload | boolean | string | undefined
@@ -307,10 +325,14 @@ const renderVideo = (data: NotionBlockPayload): string => {
307
325
  const title = caption ? ` title=${jsxString(richToPlain(data.caption))}` : "";
308
326
  // Everything else (a Notion upload, or a direct link to a media file) is a
309
327
  // real video file: `materializeAssets` rewrites the `src`, which matters most
310
- // for uploads, whose Notion URLs are signed and expire.
328
+ // for uploads, whose Notion URLs are signed and expire. It finds the file by
329
+ // that quoted `src`, where MDX decodes no backslash escapes — a JSON
330
+ // string's `\\` and `\"` would corrupt the URL or end the attribute — so the
331
+ // URL goes in as written, with a double quote as the `%22` it means anyway.
332
+ // The embed's `url` takes the expression form, like every string prop here.
311
333
  const media = isYouTubeUrl(url)
312
- ? `<YouTube${title} url=${JSON.stringify(url)} />`
313
- : `<video controls src=${JSON.stringify(url)} />`;
334
+ ? `<YouTube${title} url=${jsxString(url)} />`
335
+ : `<video controls src="${url.replaceAll('"', "%22")}" />`;
314
336
  return caption
315
337
  ? `<Frame caption=${jsxString(caption.replaceAll(FRAME_ESCAPED, ""))}>\n${media}\n</Frame>`
316
338
  : media;
@@ -577,9 +599,14 @@ export const notionSource = (
577
599
  const slugProp = richToPlain(
578
600
  page.properties[props.slug ?? "Slug"]?.rich_text
579
601
  );
580
- // Path-aware: a `guides/setup` slug keeps its `/` (per-segment slugging)
581
- // instead of mashing into `guidessetup`.
582
- const slug = slugifyPath(slugProp || title) || page.id;
602
+ // A Slug property is path-aware: `guides/setup` keeps its `/` (per-segment
603
+ // slugging) instead of mashing into `guidessetup`. A title is one name, so
604
+ // its slashes become hyphens: "CI/CD Setup" routes to `ci-cd-setup`, not
605
+ // to `cd-setup` inside an invented `ci` group.
606
+ const slug =
607
+ (slugProp
608
+ ? slugifyPath(slugProp)
609
+ : slugify(title.replaceAll("/", "-"))) || page.id;
583
610
  return { data, slug };
584
611
  };
585
612
 
@@ -21,6 +21,7 @@ import {
21
21
  resolveEntryRoute,
22
22
  slugifyPath,
23
23
  strippedLineOffset,
24
+ unloadablePathDiagnostic,
24
25
  } from "./normalize.ts";
25
26
  import type {
26
27
  ContentSource,
@@ -587,10 +588,11 @@ const transformBody = (
587
588
  * `[#custom-id]` pin and the `setup-1` a later collision gets are honored
588
589
  * rather than re-slugged. When no heading holds anything the rewrite changes,
589
590
  * the raw body's headings are the staged body's; otherwise the body is lowered
590
- * once more so the headings are scanned exactly as they ship. A link inside a
591
- * heading is rewritten here before the other notes' anchors are known, so its
592
- * own anchor is left off; that only moves the manifest id of a heading that
593
- * itself holds a heading link, which the docs already flag as unaddressable.
591
+ * once more so the headings are scanned exactly as they ship. A heading link
592
+ * inside a heading is rewritten here before every note's anchors are known,
593
+ * so its href may still lack the anchor it ships with. That moves no id: a
594
+ * heading is slugged by its rendered text, where the link is its label alone,
595
+ * so the heading stays as addressable as any other.
594
596
  */
595
597
  const fillAnchors = (pair: IndexedPair, index: LinkIndex): void => {
596
598
  const raw = extractHeadings(pair.note.content);
@@ -958,7 +960,19 @@ export const obsidianSource = (
958
960
  const exclude = new Set([...BLUME_IGNORE_DIRS, ...(options.exclude ?? [])]);
959
961
 
960
962
  const load = async (): Promise<SourceLoadResult> => {
961
- const files = await walkVault(vaultDir, vaultDir, exclude);
963
+ // A note is staged under its vault path, where Astro's content loader
964
+ // can't read a name holding `#` or `?`; such a note is reported and left
965
+ // out, and links to it degrade like links to any missing note.
966
+ const skipped: Diagnostic[] = [];
967
+ const files: string[] = [];
968
+ for (const rel of await walkVault(vaultDir, vaultDir, exclude)) {
969
+ const unloadable = unloadablePathDiagnostic(rel, join(vaultDir, rel));
970
+ if (unloadable) {
971
+ skipped.push(unloadable);
972
+ } else {
973
+ files.push(rel);
974
+ }
975
+ }
962
976
  const read = await Promise.all(
963
977
  files.map((rel) => readNote(vaultDir, rel, options))
964
978
  );
@@ -971,7 +985,10 @@ export const obsidianSource = (
971
985
  noteToEntry(pair, index, options, unresolved)
972
986
  );
973
987
  return {
974
- diagnostics: unresolvedDiagnostics(options.name, unresolved),
988
+ diagnostics: [
989
+ ...skipped,
990
+ ...unresolvedDiagnostics(options.name, unresolved),
991
+ ],
975
992
  entries,
976
993
  };
977
994
  };
@@ -4,11 +4,12 @@
4
4
  * user-supplied serializer or are skipped with a noted comment. Output is
5
5
  * Markdown text that flows through Blume's normal pipeline.
6
6
  */
7
+ import type { InlineRun } from "./lower.ts";
7
8
  import {
8
9
  guardBlockStart,
9
10
  image,
11
+ linkParts,
10
12
  renderInline,
11
- renderLink,
12
13
  unsupported,
13
14
  writesMdx,
14
15
  } from "./lower.ts";
@@ -73,38 +74,54 @@ const HEADING_STYLES = new Map([
73
74
  ["h6", "###### "],
74
75
  ]);
75
76
 
76
- /**
77
- * A span as Markdown for its marks, through the shared lowering every CMS
78
- * uses: prose is escaped, a code span outruns the backticks inside it, edge
79
- * whitespace stays outside emphasis, and a link def wraps last (so its label
80
- * keeps emphasis) only when its destination is safe to click.
81
- */
82
- const renderSpan = (
83
- span: PortableTextSpan,
84
- defs: Map<string, PortableTextMarkDef>
85
- ): string => {
77
+ /** A span's text and the marks Markdown has syntax for. */
78
+ const spanRun = (span: PortableTextSpan): InlineRun => {
86
79
  const marks = span.marks ?? [];
87
- const link = marks
88
- .map((mark) => defs.get(mark))
89
- .findLast((def) => def?._type === "link");
90
- return renderLink(
91
- renderInline(span.text ?? "", {
80
+ return {
81
+ marks: {
92
82
  bold: marks.includes("strong"),
93
83
  code: marks.includes("code"),
94
84
  italic: marks.includes("em"),
95
85
  strike: marks.includes("strike-through"),
96
- }),
97
- link?.href
98
- );
86
+ },
87
+ text: span.text ?? "",
88
+ };
99
89
  };
100
90
 
101
- /** Render the inline children of a block to a single Markdown string. */
91
+ /** Neighboring spans under one link def (or none), and that def. */
92
+ interface LinkedSpans {
93
+ link?: PortableTextMarkDef;
94
+ runs: InlineRun[];
95
+ }
96
+
97
+ /**
98
+ * Render the inline children of a block to a single Markdown string, through
99
+ * the shared lowering every CMS uses: prose is escaped, a code span outruns
100
+ * the backticks inside it, edge whitespace stays outside emphasis, neighbors
101
+ * share their marks' delimiters, and neighbors under one link def share one
102
+ * link — wrapped last (so its label keeps emphasis), and only when its
103
+ * destination is safe to click.
104
+ */
102
105
  const renderChildren = (block: PortableTextBlock): string => {
103
106
  const defs = new Map(
104
107
  (block.markDefs ?? []).map((def) => [def._key, def] as const)
105
108
  );
109
+ const linked: LinkedSpans[] = [];
110
+ for (const span of block.children ?? []) {
111
+ const link = (span.marks ?? [])
112
+ .map((mark) => defs.get(mark))
113
+ .findLast((def) => def?._type === "link");
114
+ const last = linked.at(-1);
115
+ if (last && last.link === link) {
116
+ last.runs.push(spanRun(span));
117
+ } else {
118
+ linked.push({ link, runs: [spanRun(span)] });
119
+ }
120
+ }
106
121
  return guardBlockStart(
107
- (block.children ?? []).map((span) => renderSpan(span, defs)).join("")
122
+ renderInline(
123
+ linked.flatMap(({ link, runs }) => linkParts(runs, link?.href))
124
+ )
108
125
  );
109
126
  };
110
127
 
@@ -2,10 +2,14 @@ import { readFile } from "node:fs/promises";
2
2
 
3
3
  import { expandIncludes, hasIncludeStatements } from "../includes.ts";
4
4
  import type { PageRecord } from "../types.ts";
5
+ import { substituteVariablesInBody } from "../variables.ts";
6
+ import type { ContentVariables } from "../variables.ts";
5
7
  import type { ContentSource } from "./types.ts";
6
8
 
7
9
  /** The subset of a scanned project the entry reader needs. */
8
10
  export interface EntryReadContext {
11
+ /** The site's content variables, substituted into what's read. */
12
+ config?: { variables?: ContentVariables };
9
13
  /** The instantiated sources, keyed for `read()` lookups. */
10
14
  sources?: ContentSource[];
11
15
  }
@@ -48,9 +52,10 @@ export const readExpandedEntryText = async (
48
52
  ctx: EntryReadContext,
49
53
  page: PageRecord
50
54
  ): Promise<string> => {
55
+ const variables = ctx.config?.variables;
51
56
  const raw = await readEntryText(ctx, page);
52
57
  if (!(page.sourcePath && hasIncludeStatements(raw))) {
53
- return raw;
58
+ return substituteVariablesInBody(raw, variables);
54
59
  }
55
60
  const source = ctx.sources?.find(
56
61
  (candidate) => candidate.name === page.source.name
@@ -61,11 +66,11 @@ export const readExpandedEntryText = async (
61
66
  // here would splice text the rest of the pipeline never validated, with no
62
67
  // content-root bound on resolution.
63
68
  if (!source || source.staged || !source.contentRoot) {
64
- return raw;
69
+ return substituteVariablesInBody(raw, variables);
65
70
  }
66
71
  const expansion = await expandIncludes(raw, {
67
72
  contentRoot: source.contentRoot,
68
73
  sourcePath: page.sourcePath,
69
74
  });
70
- return expansion.text;
75
+ return substituteVariablesInBody(expansion.text, variables);
71
76
  };
@@ -5,6 +5,7 @@
5
5
  */
6
6
  import type { JsonObject, JsonValue } from "./json.ts";
7
7
  import { asNumber, asObject, asString, objectsIn } from "./json.ts";
8
+ import type { InlinePart } from "./lower.ts";
8
9
  import {
9
10
  absoluteUrl,
10
11
  blockquote,
@@ -12,10 +13,10 @@ import {
12
13
  guardBlockStart,
13
14
  headingPrefix,
14
15
  image,
16
+ linkParts,
15
17
  listItem,
16
18
  markdownDocument,
17
19
  renderInline,
18
- renderLink,
19
20
  unsupported,
20
21
  } from "./lower.ts";
21
22
 
@@ -28,34 +29,37 @@ const children = (node: JsonObject): JsonObject[] => objectsIn(node.children);
28
29
 
29
30
  const typeOf = (node: JsonObject): string => asString(node.type) ?? "";
30
31
 
31
- const renderInlines = (nodes: JsonObject[]): string =>
32
- nodes
33
- // oxlint-disable-next-line no-use-before-define -- mutual recursion: a link's label is itself inline content
34
- .map((node) => renderInlineNode(node))
35
- .join("");
32
+ const inlineParts = (nodes: JsonObject[]): InlinePart[] =>
33
+ // oxlint-disable-next-line no-use-before-define -- mutual recursion: a link's label is itself inline content
34
+ nodes.flatMap((node) => inlineNodeParts(node));
36
35
 
37
- const renderInlineNode = (node: JsonObject): string => {
36
+ const inlineNodeParts = (node: JsonObject): InlinePart[] => {
38
37
  switch (typeOf(node)) {
39
38
  case "text": {
40
- return renderInline(asString(node.text) ?? "", {
41
- bold: node.bold === true,
42
- code: node.code === true,
43
- italic: node.italic === true,
44
- strike: node.strikethrough === true,
45
- });
39
+ return [
40
+ {
41
+ marks: {
42
+ bold: node.bold === true,
43
+ code: node.code === true,
44
+ italic: node.italic === true,
45
+ strike: node.strikethrough === true,
46
+ },
47
+ text: asString(node.text) ?? "",
48
+ },
49
+ ];
46
50
  }
47
51
  case "link": {
48
- return renderLink(renderInlines(children(node)), asString(node.url));
52
+ return linkParts(inlineParts(children(node)), asString(node.url));
49
53
  }
50
54
  default: {
51
- return renderInlines(children(node));
55
+ return inlineParts(children(node));
52
56
  }
53
57
  }
54
58
  };
55
59
 
56
60
  /** A text block's inline nodes, joined and guarded as one block. */
57
61
  const blockText = (nodes: JsonObject[]): string =>
58
- guardBlockStart(renderInlines(nodes));
62
+ guardBlockStart(renderInline(inlineParts(nodes)));
59
63
 
60
64
  const renderList = (node: JsonObject): string => {
61
65
  const ordered = asString(node.format) === "ordered";
@@ -51,18 +51,26 @@ export const baselineScanIgnore = (): string[] =>
51
51
  */
52
52
  export const BLUME_WATCH_IGNORE_DIRS = BLUME_IGNORE_DIRS;
53
53
 
54
- /** Extract single-segment ignore dirs (`foo`) from `foo/**`-style excludes. */
54
+ /**
55
+ * Extract root-anchored ignore dirs (`/foo`) from `foo/**`-style excludes. The
56
+ * scan's glob `foo/**` matches `foo` at the source root only, so the watcher
57
+ * skips that one directory: a nested `guides/foo/` is still content, and an
58
+ * edit there must still reload.
59
+ */
55
60
  export const excludeDirSegments = (patterns: readonly string[]): string[] =>
56
61
  patterns
57
62
  .map((pattern) => /^(?<dir>[^*/]+)\/\*\*$/u.exec(pattern)?.groups?.dir)
58
- .filter((dir): dir is string => dir !== undefined);
63
+ .filter((dir): dir is string => dir !== undefined)
64
+ .map((dir) => `/${dir}`);
59
65
 
60
66
  /**
61
67
  * Build a recursive-watch listener that fires `onChange` for content changes but
62
- * ignores events whose path crosses an ignored directory segment — one in
63
- * `ignoreDirs`, or one `ignoreSegment` rejects, for a scan whose skip rule is
64
- * a shape (every dot-name) rather than a list. A missing `filename` — rare;
65
- * the platform couldn't name the changed path — falls through to `onChange`
68
+ * ignores events whose path crosses an ignored directory — a segment named in
69
+ * `ignoreDirs` at any depth, or one `ignoreSegment` rejects, for a scan whose
70
+ * skip rule is a shape (every dot-name) rather than a list. An entry written
71
+ * with a leading slash (`/drafts`, see {@link excludeDirSegments}) names a
72
+ * directory at the watched root only. A missing `filename` — rare; the
73
+ * platform couldn't name the changed path — falls through to `onChange`
66
74
  * rather than silently dropping a real edit. Exported for testing.
67
75
  */
68
76
  export const ignoringWatchListener = (
@@ -76,7 +84,12 @@ export const ignoringWatchListener = (
76
84
  filename !== null &&
77
85
  filename
78
86
  .split(/[/\\]/u)
79
- .some((segment) => ignore.has(segment) || ignoreSegment(segment))
87
+ .some(
88
+ (segment, index) =>
89
+ ignore.has(segment) ||
90
+ (index === 0 && ignore.has(`/${segment}`)) ||
91
+ ignoreSegment(segment)
92
+ )
80
93
  ) {
81
94
  return;
82
95
  }
@@ -50,11 +50,15 @@ export interface StandardSchema<Input = unknown, Output = Input> {
50
50
  * Whether a config-supplied value implements the `~standard` contract. Generic
51
51
  * so it can decode any input at the config boundary (`z.custom` hands it the
52
52
  * raw config value) while narrowing whatever type the caller holds.
53
+ *
54
+ * The contract is the `~standard` property, whatever carries it: Zod and
55
+ * Valibot schemas are objects, but an ArkType type is a callable function. The
56
+ * optional-chained probe reads the property off either, and off `null`,
57
+ * `undefined`, or a primitive it finds nothing.
53
58
  */
54
59
  export const isStandardSchema = <T>(value: T): value is T & StandardSchema =>
55
- typeof value === "object" &&
56
- value !== null &&
57
- // SAFETY: the assertion only widens the checked object for property probing;
58
- // the trailing typeof check is what verifies `~standard.validate` exists.
59
- typeof (value as { "~standard"?: { validate?: unknown } })["~standard"]
60
- ?.validate === "function";
60
+ // SAFETY: the assertion only widens the value for property probing; the
61
+ // typeof check is what verifies `~standard.validate` exists.
62
+ typeof (
63
+ value as { "~standard"?: { validate?: unknown } } | null | undefined
64
+ )?.["~standard"]?.validate === "function";
package/src/core/types.ts CHANGED
@@ -316,7 +316,7 @@ export interface Navigation {
316
316
  cta?: HeaderAction | null;
317
317
  /** Pinned links shown above the sidebar sections, unscoped by tab. */
318
318
  featured: FeaturedLink[];
319
- /** Repo URL for the header link, or null when hidden (`navigation.repo`). */
319
+ /** Repo URL for the footer's GitHub link, or null when hidden (`navigation.repo`). */
320
320
  repoUrl?: string | null;
321
321
  /**
322
322
  * Where the header's brand link points in this locale: `logo.href` moved
@@ -5,6 +5,7 @@ export const ar: UIStringsOverride = {
5
5
  actions: {
6
6
  addToCursor: "إضافة إلى Cursor",
7
7
  addToVscode: "إضافة إلى VS Code",
8
+ askAboutCode: "اسأل عن هذه الشيفرة",
8
9
  connectMcp: "الاتصال بـ MCP",
9
10
  copied: "تم النسخ!",
10
11
  copyClaudeCode: "نسخ أمر Claude Code",
@@ -30,11 +31,18 @@ export const ar: UIStringsOverride = {
30
31
  copy: "نسخ المحادثة",
31
32
  empty: "اطرح سؤالاً حول الوثائق.",
32
33
  error: "عذراً، حدث خطأ ما.",
34
+ explainCode: "اشرح هذه الشيفرة.",
33
35
  label: "اطرح سؤالاً",
36
+ open: "فتح المساعد",
34
37
  placeholder: "اطرح سؤالاً…",
38
+ rateLimited: "لقد طرحت الكثير من الأسئلة. حاول مرة أخرى بعد بضع دقائق.",
39
+ removeCode: "إزالة الشيفرة",
35
40
  send: "إرسال",
41
+ support: "تواصل مع الدعم",
42
+ supportSubject: "سؤال من التوثيق",
36
43
  tip: "نصيحة: يمكنك فتح المحادثة وإغلاقها باستخدام",
37
44
  title: "المساعد",
45
+ verifyFailed: "تعذّر التحقق من أنك إنسان. حاول مرة أخرى.",
38
46
  you: "أنت",
39
47
  },
40
48
  banner: { dismiss: "إغلاق الإعلان" },
@@ -42,6 +50,14 @@ export const ar: UIStringsOverride = {
42
50
  description: "تحديثات المنتج وملاحظات الإصدارات.",
43
51
  title: "سجل التغييرات",
44
52
  },
53
+ consent: {
54
+ accept: "قبول",
55
+ decline: "رفض",
56
+ label: "الموافقة على ملفات تعريف الارتباط",
57
+ message: "نودّ استخدام ملفات تعريف الارتباط لفهم كيفية استخدام هذا التوثيق.",
58
+ policy: "سياسة الخصوصية",
59
+ settings: "إعدادات ملفات تعريف الارتباط",
60
+ },
45
61
  content: {
46
62
  code: "الكود",
47
63
  copyColor: "نسخ اللون {name} {value}",
@@ -52,6 +68,8 @@ export const ar: UIStringsOverride = {
52
68
  preview: "معاينة",
53
69
  prop: "الخاصية",
54
70
  selectTab: "اختيار علامة تبويب",
71
+ selectView: "اختيار طريقة العرض",
72
+ showLess: "عرض أقل",
55
73
  showMore: "عرض المزيد",
56
74
  stars: "النجوم",
57
75
  tab: "علامة التبويب {n}",
@@ -59,12 +77,36 @@ export const ar: UIStringsOverride = {
59
77
  update: "تحديث",
60
78
  },
61
79
  feedback: {
80
+ comment: "أخبرنا بالمزيد (اختياري)",
62
81
  no: "لا",
63
82
  question: "هل كانت هذه الصفحة مفيدة؟",
83
+ send: "إرسال",
64
84
  thanks: "شكراً على ملاحظاتك!",
65
85
  yes: "نعم",
66
86
  },
67
87
  languageSwitcher: { label: "اللغة", untranslated: "غير مترجم" },
88
+ narration: {
89
+ cueDanger: "خطر.",
90
+ cueInfo: "معلومة.",
91
+ cueNote: "ملاحظة.",
92
+ cueSection: "قسم قابل للتوسيع.",
93
+ cueStep: "الخطوة {n}.",
94
+ cueSuccess: "نجاح.",
95
+ cueTab: "علامة التبويب {title}.",
96
+ cueTip: "نصيحة.",
97
+ cueWarning: "تحذير.",
98
+ error: "تعذّر تحميل القراءة الصوتية.",
99
+ follow: "متابعة القراءة",
100
+ label: "استمع إلى هذه الصفحة",
101
+ minutes: "{n} دقيقة",
102
+ next: "الجملة التالية",
103
+ pause: "إيقاف مؤقت",
104
+ play: "تشغيل",
105
+ previous: "الجملة السابقة",
106
+ progress: "تقدّم القراءة",
107
+ speed: "سرعة التشغيل",
108
+ stop: "إيقاف الاستماع",
109
+ },
68
110
  nav: {
69
111
  back: "رجوع",
70
112
  breadcrumb: "مسار التنقل",
@@ -89,6 +131,7 @@ export const ar: UIStringsOverride = {
89
131
  next: "التالي",
90
132
  pagination: "ترقيم الصفحات",
91
133
  previous: "السابق",
134
+ related: "صفحات ذات صلة",
92
135
  skipToContent: "الانتقال إلى المحتوى",
93
136
  },
94
137
  search: {
@@ -5,6 +5,7 @@ export const bg: UIStringsOverride = {
5
5
  actions: {
6
6
  addToCursor: "Добави в Cursor",
7
7
  addToVscode: "Добави във VS Code",
8
+ askAboutCode: "Попитай за този код",
8
9
  connectMcp: "Свържи се с MCP",
9
10
  copied: "Копирано!",
10
11
  copyClaudeCode: "Копирай командата на Claude Code",
@@ -31,11 +32,19 @@ export const bg: UIStringsOverride = {
31
32
  copy: "Копирай разговора",
32
33
  empty: "Задайте въпрос за документацията.",
33
34
  error: "Съжаляваме, нещо се обърка.",
35
+ explainCode: "Обясни този код.",
34
36
  label: "Задайте въпрос",
37
+ open: "Отваряне на асистента",
35
38
  placeholder: "Задайте въпрос…",
39
+ rateLimited:
40
+ "Зададохте много въпроси. Опитайте отново след няколко минути.",
41
+ removeCode: "Премахни кода",
36
42
  send: "Изпрати",
43
+ support: "Свържете се с поддръжката",
44
+ supportSubject: "Въпрос от документацията",
37
45
  tip: "Съвет: Можете да отваряте и затваряте чата с",
38
46
  title: "Асистент",
47
+ verifyFailed: "Не успяхме да потвърдим, че сте човек. Опитайте отново.",
39
48
  you: "Вие",
40
49
  },
41
50
  banner: { dismiss: "Затваряне на съобщението" },
@@ -43,6 +52,15 @@ export const bg: UIStringsOverride = {
43
52
  description: "Актуализации на продукта и бележки към изданията.",
44
53
  title: "Дневник на промените",
45
54
  },
55
+ consent: {
56
+ accept: "Приемам",
57
+ decline: "Отказвам",
58
+ label: "Съгласие за бисквитки",
59
+ message:
60
+ "Бихме искали да използваме бисквитки, за да разберем как се използва тази документация.",
61
+ policy: "Политика за поверителност",
62
+ settings: "Настройки за бисквитки",
63
+ },
46
64
  content: {
47
65
  code: "Код",
48
66
  copyColor: "Копирай цвета {name} {value}",
@@ -53,6 +71,8 @@ export const bg: UIStringsOverride = {
53
71
  preview: "Преглед",
54
72
  prop: "Свойство",
55
73
  selectTab: "Избери раздел",
74
+ selectView: "Избери изглед",
75
+ showLess: "Покажи по-малко",
56
76
  showMore: "Покажи още",
57
77
  stars: "Звезди",
58
78
  tab: "Раздел {n}",
@@ -60,12 +80,36 @@ export const bg: UIStringsOverride = {
60
80
  update: "Актуализация",
61
81
  },
62
82
  feedback: {
83
+ comment: "Разкажете ни повече (по избор)",
63
84
  no: "Не",
64
85
  question: "Беше ли полезна тази страница?",
86
+ send: "Изпрати",
65
87
  thanks: "Благодарим за обратната връзка!",
66
88
  yes: "Да",
67
89
  },
68
90
  languageSwitcher: { label: "Език", untranslated: "Непреведено" },
91
+ narration: {
92
+ cueDanger: "Опасност.",
93
+ cueInfo: "Информация.",
94
+ cueNote: "Бележка.",
95
+ cueSection: "Разгъваем раздел.",
96
+ cueStep: "Стъпка {n}.",
97
+ cueSuccess: "Успех.",
98
+ cueTab: "Раздел {title}.",
99
+ cueTip: "Съвет.",
100
+ cueWarning: "Предупреждение.",
101
+ error: "Аудиоверсията не можа да бъде заредена.",
102
+ follow: "Следи четенето",
103
+ label: "Слушай тази страница",
104
+ minutes: "{n} мин",
105
+ next: "Следващо изречение",
106
+ pause: "Пауза",
107
+ play: "Пусни",
108
+ previous: "Предишно изречение",
109
+ progress: "Напредък на четенето",
110
+ speed: "Скорост на възпроизвеждане",
111
+ stop: "Спри слушането",
112
+ },
69
113
  nav: {
70
114
  back: "Назад",
71
115
  breadcrumb: "Навигационна пътека",
@@ -90,6 +134,7 @@ export const bg: UIStringsOverride = {
90
134
  next: "Напред",
91
135
  pagination: "Пагинация",
92
136
  previous: "Назад",
137
+ related: "Свързани страници",
93
138
  skipToContent: "Към съдържанието",
94
139
  },
95
140
  search: {