blume 2.1.0 → 2.1.2

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 (450) hide show
  1. package/CHANGELOG.md +110 -0
  2. package/dist/cli/{chunk-ga0pf4aj.js → chunk-0n9xkp3d.js} +12 -3
  3. package/dist/cli/{chunk-ga0pf4aj.js.map → chunk-0n9xkp3d.js.map} +3 -3
  4. package/dist/cli/{chunk-43zcf4gz.js → chunk-0pmb4tr4.js} +541 -368
  5. package/dist/cli/chunk-0pmb4tr4.js.map +35 -0
  6. package/dist/cli/{chunk-9geymqns.js → chunk-0w6hs479.js} +15 -15
  7. package/dist/cli/{chunk-xkz5xbtv.js → chunk-124fvmzq.js} +2802 -1422
  8. package/dist/cli/chunk-124fvmzq.js.map +220 -0
  9. package/dist/cli/chunk-150jk7km.js +52 -0
  10. package/dist/cli/chunk-150jk7km.js.map +10 -0
  11. package/dist/cli/{chunk-y4ykbark.js → chunk-1f882900.js} +2 -2
  12. package/dist/cli/{chunk-5ffywsmb.js → chunk-297ajdcv.js} +8 -6
  13. package/dist/cli/{chunk-5ffywsmb.js.map → chunk-297ajdcv.js.map} +3 -3
  14. package/dist/cli/{chunk-h2a48yhd.js → chunk-32khwcb6.js} +120 -16
  15. package/dist/cli/{chunk-h2a48yhd.js.map → chunk-32khwcb6.js.map} +10 -10
  16. package/dist/cli/{chunk-5cg2rzgf.js → chunk-36bxs9c7.js} +20 -49
  17. package/dist/cli/{chunk-5cg2rzgf.js.map → chunk-36bxs9c7.js.map} +4 -5
  18. package/dist/cli/{chunk-py62fewh.js → chunk-53jqw8z0.js} +3 -3
  19. package/dist/cli/{chunk-5wj2qwn2.js → chunk-58bt3hag.js} +4 -4
  20. package/dist/cli/{chunk-e7vvk5ya.js → chunk-5cmts6dv.js} +3 -3
  21. package/dist/cli/{chunk-y5h4r2y0.js → chunk-60hz265r.js} +15 -5
  22. package/dist/cli/chunk-60hz265r.js.map +10 -0
  23. package/dist/cli/chunk-74wfhv79.js +65 -0
  24. package/dist/cli/chunk-74wfhv79.js.map +10 -0
  25. package/dist/cli/{chunk-g2z9x01v.js → chunk-9qzgg6mj.js} +3 -3
  26. package/dist/cli/{chunk-q3wxxq39.js → chunk-a57yh0h9.js} +5 -5
  27. package/dist/cli/{chunk-713mngdq.js → chunk-ave1wcbr.js} +76 -10
  28. package/dist/cli/chunk-ave1wcbr.js.map +14 -0
  29. package/dist/cli/{chunk-2n4ps1b3.js → chunk-bqxaqn5p.js} +40 -22
  30. package/dist/cli/chunk-bqxaqn5p.js.map +15 -0
  31. package/dist/cli/{chunk-jnwj5kjj.js → chunk-bxwhtvw8.js} +20 -7
  32. package/dist/cli/{chunk-jnwj5kjj.js.map → chunk-bxwhtvw8.js.map} +3 -3
  33. package/dist/cli/chunk-ckrcm08h.js +324 -0
  34. package/dist/cli/chunk-ckrcm08h.js.map +14 -0
  35. package/dist/cli/{chunk-2f3ndkpw.js → chunk-desvm72z.js} +3 -3
  36. package/dist/cli/{chunk-gp84za0h.js → chunk-dhwtqnnd.js} +2 -2
  37. package/dist/cli/{chunk-4h8z9vv5.js → chunk-dpy8qdjf.js} +93 -47
  38. package/dist/cli/chunk-dpy8qdjf.js.map +17 -0
  39. package/dist/cli/{chunk-2f7n4qzt.js → chunk-dqhg54mr.js} +1 -1
  40. package/dist/cli/{chunk-0gz36akr.js → chunk-dv2z0rzf.js} +21 -5
  41. package/dist/cli/chunk-dv2z0rzf.js.map +12 -0
  42. package/dist/cli/{chunk-cm89scdv.js → chunk-es8h0v3j.js} +9 -11
  43. package/dist/cli/{chunk-cm89scdv.js.map → chunk-es8h0v3j.js.map} +3 -3
  44. package/dist/cli/{chunk-zhvd6adm.js → chunk-gfc5fqvf.js} +14 -14
  45. package/dist/cli/{chunk-nd1821sk.js → chunk-gxtg6ed2.js} +33 -19
  46. package/dist/cli/chunk-gxtg6ed2.js.map +10 -0
  47. package/dist/cli/{chunk-p804f3pe.js → chunk-hvcxng5f.js} +6 -6
  48. package/dist/cli/{chunk-ta27feev.js → chunk-j1x92mx9.js} +21 -2
  49. package/dist/cli/chunk-j1x92mx9.js.map +10 -0
  50. package/dist/cli/{chunk-rekqz4qg.js → chunk-k3gr1nm9.js} +19 -16
  51. package/dist/cli/chunk-k3gr1nm9.js.map +10 -0
  52. package/dist/cli/{chunk-1n228xqb.js → chunk-kdpzw38a.js} +2 -2
  53. package/dist/cli/{chunk-cy5bm0j0.js → chunk-ms9z66nz.js} +2 -2
  54. package/dist/cli/{chunk-cy5bm0j0.js.map → chunk-ms9z66nz.js.map} +2 -2
  55. package/dist/cli/{chunk-zf748avv.js → chunk-nbyvgnnm.js} +193 -286
  56. package/dist/cli/chunk-nbyvgnnm.js.map +36 -0
  57. package/dist/cli/{chunk-6bm0xpgd.js → chunk-s0bdnc7v.js} +2 -2
  58. package/dist/cli/{chunk-yxmk4pec.js → chunk-sc93apfs.js} +17 -16
  59. package/dist/cli/{chunk-yxmk4pec.js.map → chunk-sc93apfs.js.map} +3 -3
  60. package/dist/cli/{chunk-cmtcnh9f.js → chunk-tfycddbt.js} +2 -2
  61. package/dist/cli/{chunk-9xr4dgee.js → chunk-twy7psdz.js} +29 -16
  62. package/dist/cli/chunk-twy7psdz.js.map +10 -0
  63. package/dist/cli/{chunk-ntv4qrt7.js → chunk-y4msrwm9.js} +15 -15
  64. package/dist/cli/{chunk-yj17vs39.js → chunk-ydp3065p.js} +5 -5
  65. package/dist/cli/index.js +24 -19
  66. package/dist/cli/index.js.map +3 -3
  67. package/dist/types/ai/ask-context.d.ts +6 -2
  68. package/dist/types/ai/ask.d.ts +24 -5
  69. package/dist/types/ai/component-markdown.d.ts +2 -0
  70. package/dist/types/ai/openapi-components.d.ts +4 -1
  71. package/dist/types/analytics/clics.d.ts +37 -0
  72. package/dist/types/analytics/index.d.ts +2 -0
  73. package/dist/types/analytics/schema.d.ts +18 -0
  74. package/dist/types/astro/integration.d.ts +11 -4
  75. package/dist/types/astro/markdown-negotiation.d.ts +3 -0
  76. package/dist/types/astro/pages.d.ts +10 -0
  77. package/dist/types/cli/init/scaffold.d.ts +1 -1
  78. package/dist/types/components/content/api-field.d.ts +4 -1
  79. package/dist/types/components/content/api-page.d.ts +6 -1
  80. package/dist/types/components/openapi/code-samples.d.ts +29 -0
  81. package/dist/types/components/openapi/helpers.d.ts +22 -1
  82. package/dist/types/components/openapi/security.d.ts +10 -0
  83. package/dist/types/components/openapi/snippets.d.ts +60 -0
  84. package/dist/types/core/changelog-index.d.ts +8 -0
  85. package/dist/types/core/config-input.d.ts +37 -6
  86. package/dist/types/core/data.d.ts +4 -0
  87. package/dist/types/core/i18n-ui.d.ts +2 -0
  88. package/dist/types/core/links.d.ts +8 -0
  89. package/dist/types/core/powered-by.d.ts +12 -0
  90. package/dist/types/core/probe.d.ts +2 -0
  91. package/dist/types/core/project-graph.d.ts +6 -0
  92. package/dist/types/core/redirect-patterns.d.ts +11 -3
  93. package/dist/types/core/schema.d.ts +228 -3
  94. package/dist/types/core/sources/cache.d.ts +3 -0
  95. package/dist/types/core/sources/contentful-rich-text.d.ts +11 -2
  96. package/dist/types/core/sources/contentful.d.ts +17 -1
  97. package/dist/types/core/sources/lexical.d.ts +5 -0
  98. package/dist/types/core/sources/lower.d.ts +10 -2
  99. package/dist/types/core/sources/normalize.d.ts +20 -1
  100. package/dist/types/core/sources/notion.d.ts +8 -0
  101. package/dist/types/core/sources/remote.d.ts +37 -10
  102. package/dist/types/core/sources/sanity.d.ts +1 -1
  103. package/dist/types/core/types.d.ts +17 -0
  104. package/dist/types/deploy/adapter-output.d.ts +2 -1
  105. package/dist/types/deploy/adapters/cloudflare.d.ts +7 -5
  106. package/dist/types/deploy/adapters/node.d.ts +58 -23
  107. package/dist/types/deploy/adapters/registry.d.ts +10 -0
  108. package/dist/types/deploy/adapters/types.d.ts +3 -2
  109. package/dist/types/deploy/cloudflare-negotiation.d.ts +5 -2
  110. package/dist/types/deploy/node-headers.d.ts +12 -4
  111. package/dist/types/deploy/platforms/cloudflare.d.ts +10 -7
  112. package/dist/types/deploy/platforms/node.d.ts +3 -1
  113. package/dist/types/deploy/platforms/types.d.ts +18 -7
  114. package/dist/types/deploy/platforms/vercel.d.ts +21 -2
  115. package/dist/types/deploy/redirects.d.ts +17 -1
  116. package/dist/types/deploy/sitemap.d.ts +2 -1
  117. package/dist/types/deploy/vercel-negotiation.d.ts +44 -21
  118. package/dist/types/narration/build.d.ts +34 -12
  119. package/dist/types/narration/provider.d.ts +96 -8
  120. package/dist/types/openapi/checks.d.ts +27 -0
  121. package/dist/types/openapi/graphql.d.ts +6 -0
  122. package/dist/types/openapi/model.d.ts +15 -4
  123. package/dist/types/openapi/parse.d.ts +3 -0
  124. package/dist/types/openapi/references.d.ts +16 -0
  125. package/dist/types/openapi/sentence.d.ts +11 -1
  126. package/dist/types/ratelimit/index.d.ts +1 -1
  127. package/dist/types/ratelimit/schema.d.ts +8 -0
  128. package/dist/types/ratelimit/upstash.d.ts +39 -14
  129. package/dist/types/reference/graphql.d.ts +76 -1
  130. package/dist/types/reference/index.d.ts +1 -1
  131. package/dist/types/reference/openapi.d.ts +2 -2
  132. package/dist/types/reference/options.d.ts +37 -1
  133. package/dist/types/reference/schema.d.ts +84 -0
  134. package/dist/types/search/adapters/registry.d.ts +3 -0
  135. package/dist/types/search/adapters/typesense.d.ts +14 -4
  136. package/dist/types/search/build.d.ts +3 -1
  137. package/dist/types/search/orama-index.d.ts +9 -9
  138. package/dist/types/search/sync/algolia.d.ts +5 -2
  139. package/dist/types/search/sync/index.d.ts +3 -1
  140. package/dist/types/search/sync/typesense.d.ts +11 -7
  141. package/dist/types/seo/jsonld.d.ts +11 -6
  142. package/dist/types/sources/contentful.d.ts +16 -0
  143. package/dist/types/sources/github-releases.d.ts +8 -1
  144. package/dist/types/sources/notion.d.ts +8 -0
  145. package/dist/types/sources/payload.d.ts +2 -0
  146. package/dist/types/sources/registry.d.ts +63 -0
  147. package/dist/types/sources/shared.d.ts +3 -0
  148. package/dist/types/sources/strapi.d.ts +2 -0
  149. package/docs/01-quickstart.mdx +4 -2
  150. package/docs/02-deployment.mdx +59 -13
  151. package/docs/03-upgrading.mdx +2 -1
  152. package/docs/04-migrating.mdx +9 -9
  153. package/docs/08-faq.mdx +12 -12
  154. package/docs/advanced/changelog.mdx +30 -2
  155. package/docs/advanced/custom-pages.mdx +1 -1
  156. package/docs/advanced/skills.mdx +1 -1
  157. package/docs/cli/audit.mdx +30 -7
  158. package/docs/cli/doctor.mdx +6 -2
  159. package/docs/cli/evals.mdx +4 -4
  160. package/docs/cli/translate.mdx +1 -0
  161. package/docs/cli/validate.mdx +4 -3
  162. package/docs/configuration/analytics.mdx +19 -1
  163. package/docs/configuration/assistant.mdx +6 -2
  164. package/docs/configuration/customization.mdx +4 -2
  165. package/docs/configuration/export.mdx +5 -3
  166. package/docs/configuration/index.mdx +4 -2
  167. package/docs/configuration/narration.mdx +43 -6
  168. package/docs/configuration/rate-limiting.mdx +23 -2
  169. package/docs/configuration/search.mdx +37 -14
  170. package/docs/content/components.mdx +2 -2
  171. package/docs/content/frontmatter.mdx +5 -3
  172. package/docs/content/i18n.mdx +2 -2
  173. package/docs/content/includes.mdx +1 -1
  174. package/docs/content/navigation.mdx +8 -6
  175. package/docs/content/sources/contentful.mdx +9 -4
  176. package/docs/content/sources/custom.mdx +7 -3
  177. package/docs/content/sources/github-releases.mdx +12 -2
  178. package/docs/content/sources/index.mdx +3 -3
  179. package/docs/content/sources/notion.mdx +17 -2
  180. package/docs/content/sources/obsidian.mdx +3 -1
  181. package/docs/content/sources/payload.mdx +5 -2
  182. package/docs/content/sources/remote-mdx.mdx +6 -0
  183. package/docs/content/sources/sanity.mdx +4 -2
  184. package/docs/content/sources/strapi.mdx +5 -2
  185. package/docs/content/syntax.mdx +2 -2
  186. package/docs/discoverability/agent-discovery.mdx +1 -1
  187. package/docs/discoverability/index.mdx +1 -1
  188. package/docs/discoverability/json-api.mdx +2 -0
  189. package/docs/discoverability/llms-txt.mdx +1 -1
  190. package/docs/discoverability/markdown.mdx +3 -1
  191. package/docs/discoverability/mcp.mdx +6 -0
  192. package/docs/discoverability/sitemap-and-robots.mdx +1 -1
  193. package/docs/discoverability/structured-data.mdx +8 -6
  194. package/docs/references/api-pages.mdx +1 -1
  195. package/docs/references/asyncapi.mdx +5 -1
  196. package/docs/references/graphql.mdx +15 -1
  197. package/docs/references/openapi.mdx +64 -8
  198. package/package.json +9 -9
  199. package/skills/blume/SKILL.md +1 -1
  200. package/skills/blume-migrate/SKILL.md +2 -2
  201. package/skills/blume-migrate/assets/{oxfmt@0.67.0.patch → oxfmt@0.71.0.patch} +7 -7
  202. package/skills/blume-migrate/references/docusaurus.md +4 -4
  203. package/skills/blume-migrate/references/fumadocs.md +17 -13
  204. package/skills/blume-migrate/references/monorepo.md +5 -5
  205. package/skills/blume-migrate/references/nextra.md +8 -5
  206. package/skills/blume-migrate/references/starlight.md +9 -7
  207. package/src/ai/ask-context.ts +33 -3
  208. package/src/ai/ask-limits.ts +13 -0
  209. package/src/ai/ask.ts +24 -6
  210. package/src/ai/changelog-markdown.ts +11 -6
  211. package/src/ai/component-markdown.ts +1 -1
  212. package/src/ai/cors.ts +12 -3
  213. package/src/ai/mcp/query.ts +14 -6
  214. package/src/ai/openapi-components.ts +28 -4
  215. package/src/ai/relative-links.ts +4 -38
  216. package/src/analytics/clics.ts +58 -0
  217. package/src/analytics/head.ts +5 -0
  218. package/src/analytics/index.ts +2 -0
  219. package/src/analytics/schema.ts +2 -0
  220. package/src/astro/generate.ts +91 -56
  221. package/src/astro/integration.ts +72 -22
  222. package/src/astro/markdown-negotiation.ts +3 -0
  223. package/src/astro/module-types.ts +3 -1
  224. package/src/astro/pages.ts +23 -0
  225. package/src/astro/render-deps.ts +10 -5
  226. package/src/astro/runtime-deps.ts +23 -2
  227. package/src/astro/templates.ts +129 -81
  228. package/src/audit/catalog.ts +19 -3
  229. package/src/audit/checks/links.ts +14 -1
  230. package/src/audit/checks/network.ts +110 -6
  231. package/src/audit/checks/redirects.ts +6 -4
  232. package/src/audit/checks/robots.ts +58 -2
  233. package/src/audit/run.ts +3 -0
  234. package/src/audit/types.ts +2 -0
  235. package/src/audit/url.ts +14 -5
  236. package/src/blume-modules.d.ts +5 -1
  237. package/src/cli/args.ts +51 -0
  238. package/src/cli/build-failure.ts +23 -0
  239. package/src/cli/commands/audit.ts +10 -1
  240. package/src/cli/commands/dev.ts +18 -2
  241. package/src/cli/commands/doctor.ts +45 -12
  242. package/src/cli/commands/eval.ts +36 -4
  243. package/src/cli/commands/init.ts +6 -4
  244. package/src/cli/commands/validate.ts +10 -1
  245. package/src/cli/init/scaffold.ts +9 -6
  246. package/src/cli/prepare.ts +6 -3
  247. package/src/cli/required-secrets.ts +3 -2
  248. package/src/components/content/ApiPlayground.astro +6 -2
  249. package/src/components/content/api-field.ts +4 -1
  250. package/src/components/content/api-page.ts +31 -7
  251. package/src/components/content/mermaid-element.ts +15 -1
  252. package/src/components/content/view-switcher.ts +7 -4
  253. package/src/components/islands/hooks.ts +27 -1
  254. package/src/components/layout/NavTree.astro +49 -5
  255. package/src/components/layout/NavTreeScript.astro +18 -0
  256. package/src/components/layout/PageActions.astro +139 -24
  257. package/src/components/layout/PageLayout.astro +13 -3
  258. package/src/components/layout/RootLayout.astro +27 -14
  259. package/src/components/layout/Search.astro +17 -3
  260. package/src/components/layout/TableOfContents.astro +24 -17
  261. package/src/components/layout/analytics-client.ts +8 -8
  262. package/src/components/layout/head-scripts.ts +6 -1
  263. package/src/components/layout/search/algolia.ts +10 -1
  264. package/src/components/layout/search/analytics.ts +42 -14
  265. package/src/components/layout/search/endpoint.ts +48 -5
  266. package/src/components/layout/search/pagefind.ts +114 -5
  267. package/src/components/layout/search/types.ts +12 -0
  268. package/src/components/layout/search/typesense.ts +18 -6
  269. package/src/components/openapi/AsyncApiOperation.astro +14 -7
  270. package/src/components/openapi/Authorization.astro +10 -0
  271. package/src/components/openapi/GraphqlOperation.astro +5 -0
  272. package/src/components/openapi/Operation.astro +24 -1
  273. package/src/components/openapi/ParametersTable.astro +8 -4
  274. package/src/components/openapi/Playground.astro +7 -1
  275. package/src/components/openapi/RequestBody.astro +19 -2
  276. package/src/components/openapi/Responses.astro +1 -0
  277. package/src/components/openapi/SchemaTable.astro +15 -3
  278. package/src/components/openapi/async-snippets.ts +61 -5
  279. package/src/components/openapi/async.ts +123 -29
  280. package/src/components/openapi/graphql-helpers.ts +35 -12
  281. package/src/components/openapi/helpers.ts +36 -2
  282. package/src/components/openapi/message-model.ts +54 -11
  283. package/src/components/openapi/message.ts +14 -4
  284. package/src/components/openapi/operation-model.ts +16 -7
  285. package/src/components/openapi/playground-client.ts +77 -58
  286. package/src/components/openapi/playground-schema.ts +1 -1
  287. package/src/components/openapi/schema-tree.ts +44 -14
  288. package/src/components/openapi/security.ts +10 -0
  289. package/src/components/openapi/snippets.ts +5 -3
  290. package/src/components/openapi/validate-json.ts +4 -3
  291. package/src/components/powered-by-middleware.ts +33 -0
  292. package/src/core/changelog-index.ts +31 -1
  293. package/src/core/component-diagnostics.ts +53 -0
  294. package/src/core/component-overrides.ts +32 -0
  295. package/src/core/config-input.ts +42 -6
  296. package/src/core/data.ts +2 -0
  297. package/src/core/diagnostics.ts +5 -0
  298. package/src/core/i18n-ui.ts +2 -0
  299. package/src/core/includes.ts +1 -1
  300. package/src/core/links.ts +47 -17
  301. package/src/core/powered-by.ts +12 -0
  302. package/src/core/probe.ts +26 -6
  303. package/src/core/project-graph.ts +59 -18
  304. package/src/core/redirect-patterns.ts +36 -7
  305. package/src/core/schema.ts +26 -6
  306. package/src/core/sources/cache.ts +6 -0
  307. package/src/core/sources/contentful-rich-text.ts +29 -6
  308. package/src/core/sources/contentful.ts +135 -25
  309. package/src/core/sources/filesystem.ts +26 -4
  310. package/src/core/sources/github-releases.ts +12 -1
  311. package/src/core/sources/lexical.ts +15 -10
  312. package/src/core/sources/lower.ts +19 -3
  313. package/src/core/sources/mdx-remote.ts +62 -10
  314. package/src/core/sources/normalize.ts +227 -41
  315. package/src/core/sources/notion.ts +34 -10
  316. package/src/core/sources/obsidian.ts +16 -3
  317. package/src/core/sources/payload.ts +38 -15
  318. package/src/core/sources/remote.ts +72 -15
  319. package/src/core/sources/resolve.ts +11 -1
  320. package/src/core/sources/sanity.ts +43 -25
  321. package/src/core/sources/strapi.ts +2 -2
  322. package/src/core/types.ts +18 -0
  323. package/src/core/ui-packs/ar.ts +1 -0
  324. package/src/core/ui-packs/bg.ts +1 -0
  325. package/src/core/ui-packs/bn.ts +1 -0
  326. package/src/core/ui-packs/ca.ts +1 -0
  327. package/src/core/ui-packs/cs.ts +1 -0
  328. package/src/core/ui-packs/da.ts +1 -0
  329. package/src/core/ui-packs/de.ts +1 -0
  330. package/src/core/ui-packs/el.ts +1 -0
  331. package/src/core/ui-packs/es.ts +1 -0
  332. package/src/core/ui-packs/fa.ts +1 -0
  333. package/src/core/ui-packs/fi.ts +1 -0
  334. package/src/core/ui-packs/fr.ts +1 -0
  335. package/src/core/ui-packs/he.ts +1 -0
  336. package/src/core/ui-packs/hi.ts +1 -0
  337. package/src/core/ui-packs/hr.ts +1 -0
  338. package/src/core/ui-packs/hu.ts +1 -0
  339. package/src/core/ui-packs/id.ts +1 -0
  340. package/src/core/ui-packs/it.ts +1 -0
  341. package/src/core/ui-packs/ja.ts +1 -0
  342. package/src/core/ui-packs/ko.ts +1 -0
  343. package/src/core/ui-packs/nl.ts +1 -0
  344. package/src/core/ui-packs/no.ts +1 -0
  345. package/src/core/ui-packs/pl.ts +1 -0
  346. package/src/core/ui-packs/pt-br.ts +1 -0
  347. package/src/core/ui-packs/pt.ts +1 -0
  348. package/src/core/ui-packs/ro.ts +1 -0
  349. package/src/core/ui-packs/ru.ts +1 -0
  350. package/src/core/ui-packs/sk.ts +1 -0
  351. package/src/core/ui-packs/sr.ts +1 -0
  352. package/src/core/ui-packs/sv.ts +1 -0
  353. package/src/core/ui-packs/th.ts +1 -0
  354. package/src/core/ui-packs/tr.ts +1 -0
  355. package/src/core/ui-packs/uk.ts +1 -0
  356. package/src/core/ui-packs/vi.ts +1 -0
  357. package/src/core/ui-packs/zh-tw.ts +1 -0
  358. package/src/core/ui-packs/zh.ts +1 -0
  359. package/src/deploy/adapter-output.ts +7 -2
  360. package/src/deploy/adapters/cloudflare.ts +7 -5
  361. package/src/deploy/adapters/node.ts +47 -9
  362. package/src/deploy/adapters/types.ts +7 -3
  363. package/src/deploy/artifacts.ts +2 -1
  364. package/src/deploy/cloudflare-negotiation.ts +36 -7
  365. package/src/deploy/headers.ts +15 -1
  366. package/src/deploy/node-headers.ts +32 -20
  367. package/src/deploy/platforms/cloudflare.ts +14 -9
  368. package/src/deploy/platforms/netlify.ts +2 -1
  369. package/src/deploy/platforms/node.ts +12 -2
  370. package/src/deploy/platforms/types.ts +21 -7
  371. package/src/deploy/platforms/vercel.ts +115 -17
  372. package/src/deploy/redirects.ts +66 -6
  373. package/src/deploy/rss.ts +3 -1
  374. package/src/deploy/sitemap.ts +39 -5
  375. package/src/deploy/vercel-negotiation.ts +325 -103
  376. package/src/eval/agents.ts +18 -1
  377. package/src/eval/prompts.ts +6 -3
  378. package/src/eval/report.ts +34 -13
  379. package/src/eval/run.ts +5 -0
  380. package/src/markdown/base-links.ts +15 -14
  381. package/src/markdown/package-commands.ts +9 -8
  382. package/src/markdown/relative-links.ts +3 -2
  383. package/src/narration/build.ts +218 -40
  384. package/src/narration/provider.ts +89 -9
  385. package/src/openapi/checks.ts +273 -0
  386. package/src/openapi/graphql-build.ts +21 -0
  387. package/src/openapi/graphql.ts +11 -0
  388. package/src/openapi/model.ts +15 -3
  389. package/src/openapi/parse.ts +127 -2
  390. package/src/openapi/references.ts +43 -3
  391. package/src/openapi/render-mdx.ts +68 -8
  392. package/src/openapi/sentence.ts +29 -2
  393. package/src/openapi/source.ts +39 -14
  394. package/src/ratelimit/index.ts +1 -1
  395. package/src/ratelimit/runtime.ts +37 -19
  396. package/src/ratelimit/upstash.ts +40 -10
  397. package/src/reference/graphql.ts +15 -1
  398. package/src/reference/index.ts +1 -0
  399. package/src/reference/openapi.ts +2 -2
  400. package/src/reference/options.ts +29 -1
  401. package/src/registry/eject.ts +8 -2
  402. package/src/search/adapters/locale-scope.ts +30 -0
  403. package/src/search/adapters/typesense.ts +14 -4
  404. package/src/search/build.ts +21 -3
  405. package/src/search/orama-index.ts +163 -31
  406. package/src/search/source-pages.ts +29 -0
  407. package/src/search/sync/algolia.ts +99 -9
  408. package/src/search/sync/index.ts +52 -13
  409. package/src/search/sync/typesense.ts +72 -27
  410. package/src/seo/jsonld.ts +38 -20
  411. package/src/sources/contentful.ts +12 -0
  412. package/src/sources/github-releases.ts +7 -1
  413. package/src/sources/notion.ts +7 -0
  414. package/src/sources/shared.ts +3 -0
  415. package/src/theme/color.ts +9 -10
  416. package/src/theme/entry.ts +43 -6
  417. package/src/translate/validate.ts +23 -1
  418. package/dist/cli/chunk-0gz36akr.js.map +0 -12
  419. package/dist/cli/chunk-2n4ps1b3.js.map +0 -15
  420. package/dist/cli/chunk-43zcf4gz.js.map +0 -36
  421. package/dist/cli/chunk-4ejxbzee.js +0 -39
  422. package/dist/cli/chunk-4ejxbzee.js.map +0 -10
  423. package/dist/cli/chunk-4h8z9vv5.js.map +0 -16
  424. package/dist/cli/chunk-713mngdq.js.map +0 -14
  425. package/dist/cli/chunk-9xr4dgee.js.map +0 -10
  426. package/dist/cli/chunk-amrb6hgh.js +0 -145
  427. package/dist/cli/chunk-amrb6hgh.js.map +0 -11
  428. package/dist/cli/chunk-nd1821sk.js.map +0 -10
  429. package/dist/cli/chunk-rekqz4qg.js.map +0 -10
  430. package/dist/cli/chunk-ta27feev.js.map +0 -10
  431. package/dist/cli/chunk-xkz5xbtv.js.map +0 -213
  432. package/dist/cli/chunk-y5h4r2y0.js.map +0 -10
  433. package/dist/cli/chunk-zf748avv.js.map +0 -37
  434. /package/dist/cli/{chunk-9geymqns.js.map → chunk-0w6hs479.js.map} +0 -0
  435. /package/dist/cli/{chunk-y4ykbark.js.map → chunk-1f882900.js.map} +0 -0
  436. /package/dist/cli/{chunk-py62fewh.js.map → chunk-53jqw8z0.js.map} +0 -0
  437. /package/dist/cli/{chunk-5wj2qwn2.js.map → chunk-58bt3hag.js.map} +0 -0
  438. /package/dist/cli/{chunk-e7vvk5ya.js.map → chunk-5cmts6dv.js.map} +0 -0
  439. /package/dist/cli/{chunk-g2z9x01v.js.map → chunk-9qzgg6mj.js.map} +0 -0
  440. /package/dist/cli/{chunk-q3wxxq39.js.map → chunk-a57yh0h9.js.map} +0 -0
  441. /package/dist/cli/{chunk-2f3ndkpw.js.map → chunk-desvm72z.js.map} +0 -0
  442. /package/dist/cli/{chunk-gp84za0h.js.map → chunk-dhwtqnnd.js.map} +0 -0
  443. /package/dist/cli/{chunk-2f7n4qzt.js.map → chunk-dqhg54mr.js.map} +0 -0
  444. /package/dist/cli/{chunk-zhvd6adm.js.map → chunk-gfc5fqvf.js.map} +0 -0
  445. /package/dist/cli/{chunk-p804f3pe.js.map → chunk-hvcxng5f.js.map} +0 -0
  446. /package/dist/cli/{chunk-1n228xqb.js.map → chunk-kdpzw38a.js.map} +0 -0
  447. /package/dist/cli/{chunk-6bm0xpgd.js.map → chunk-s0bdnc7v.js.map} +0 -0
  448. /package/dist/cli/{chunk-cmtcnh9f.js.map → chunk-tfycddbt.js.map} +0 -0
  449. /package/dist/cli/{chunk-ntv4qrt7.js.map → chunk-y4msrwm9.js.map} +0 -0
  450. /package/dist/cli/{chunk-yj17vs39.js.map → chunk-ydp3065p.js.map} +0 -0
@@ -104,12 +104,37 @@ const parseFlags = (args: EvalFlags) => {
104
104
  return { agent: args.agent, threshold, timeoutS };
105
105
  };
106
106
 
107
+ /** A shell word for the rerun command, single-quoted only when it has to be. */
108
+ const shellWord = (value: string): string =>
109
+ /^[\w./:@%+=,-]+$/u.test(value)
110
+ ? value
111
+ : `'${value.replaceAll("'", String.raw`'\''`)}'`;
112
+
113
+ /**
114
+ * The command a `--fix` agent reruns to verify its edits. It carries the run's
115
+ * own flags: a bare `blume eval` would fall back to Codex and `evals.yaml`,
116
+ * grading a different file with a different agent than the run it's fixing.
117
+ */
118
+ const rerunCommand = (
119
+ args: EvalFlags,
120
+ { agent, threshold, timeoutS }: ReturnType<typeof parseFlags>
121
+ ): string =>
122
+ [
123
+ `blume eval --agent ${agent}`,
124
+ args.file === DEFAULT_FILE ? "" : `--file ${shellWord(args.file)}`,
125
+ args.threshold === undefined ? "" : `--threshold ${threshold}`,
126
+ args.timeout === undefined ? "" : `--timeout ${timeoutS}`,
127
+ ]
128
+ .filter((part) => part !== "")
129
+ .join(" ");
130
+
107
131
  /** `blume eval --fix`: hand the failing report to the interactive agent. */
108
132
  const runFixHandoff = async (
109
133
  agent: AgentKind,
110
134
  result: EvalResult,
111
135
  root: string,
112
- threshold: number
136
+ threshold: number,
137
+ command: string
113
138
  ): Promise<void> => {
114
139
  const count = result.counts.fail + result.counts.error;
115
140
  if (count === 0) {
@@ -120,7 +145,7 @@ const runFixHandoff = async (
120
145
  process.stderr.write(
121
146
  ` Handing ${count} failed question${count === 1 ? "" : "s"} to ${cli.name}…\n\n`
122
147
  );
123
- const code = await launchAgentCode(agent, evalFixPrompt(report));
148
+ const code = await launchAgentCode(agent, evalFixPrompt(report, command));
124
149
  if (code !== 0) {
125
150
  process.exit(code);
126
151
  }
@@ -184,7 +209,8 @@ export const evalCommand = defineCommand({
184
209
  meta: commandMeta.eval,
185
210
  async run({ args }) {
186
211
  const root = process.cwd();
187
- const { agent, threshold, timeoutS } = parseFlags(args);
212
+ const flags = parseFlags(args);
213
+ const { agent, threshold, timeoutS } = flags;
188
214
  if (args.action === "init") {
189
215
  await runInit(agent, args.file);
190
216
  return;
@@ -259,7 +285,13 @@ export const evalCommand = defineCommand({
259
285
  if (args.fix) {
260
286
  // The gate is a CI concern; a handoff run succeeds when the agent
261
287
  // session does, not when the docs already passed.
262
- await runFixHandoff(agent, result, root, threshold);
288
+ await runFixHandoff(
289
+ agent,
290
+ result,
291
+ root,
292
+ threshold,
293
+ rerunCommand(args, flags)
294
+ );
263
295
  return;
264
296
  }
265
297
 
@@ -272,14 +272,16 @@ export const initCommand = defineCommand({
272
272
  // which `init` leaves alone; say what it still needs before installing.
273
273
  const note = workspaceNote(root, answers, env);
274
274
 
275
- // Keep installed dependencies, Blume's generated runtime (`.blume/`), and
276
- // build output (`dist/`) out of version control. Idempotent: creates
277
- // `.gitignore` when absent and skips entries already present
278
- // (trailing-slash agnostic).
275
+ // Keep installed dependencies, Blume's generated runtime (`.blume/`),
276
+ // build output (`dist/`), and local secrets (`.env.local`, where every
277
+ // missing-secret warning says to put them) out of version control.
278
+ // Idempotent: creates `.gitignore` when absent and skips entries already
279
+ // present (trailing-slash agnostic).
279
280
  const ignored = await ensureGitignore(root, [
280
281
  "node_modules/",
281
282
  ".blume/",
282
283
  "dist/",
284
+ ".env.local",
283
285
  ]);
284
286
  if (ignored.length > 0) {
285
287
  sink.success(`Added ${ignored.join(", ")} to .gitignore`);
@@ -13,6 +13,7 @@ import { validateLinks } from "../../core/links.ts";
13
13
  import { buildManifest } from "../../core/manifest.ts";
14
14
  import { scanProject } from "../../core/project-graph.ts";
15
15
  import type { Diagnostic } from "../../core/types.ts";
16
+ import { parseIgnoreFlag } from "../args.ts";
16
17
  import { commandMeta } from "../command-meta.ts";
17
18
  import { reportInternalError } from "../internal-error.ts";
18
19
  import {
@@ -28,6 +29,12 @@ export const validateCommand = defineCommand({
28
29
  description: "Check external (HTTP) links over the network.",
29
30
  type: "boolean",
30
31
  },
32
+ ignore: {
33
+ description:
34
+ 'With --external, skip external links whose URL matches this glob (e.g. "https://example.com/**"). Repeat it for more; internal links are always checked.',
35
+ type: "string",
36
+ valueHint: "glob",
37
+ },
31
38
  json: {
32
39
  description: "Emit diagnostics as JSON on stdout (for CI/editors).",
33
40
  type: "boolean",
@@ -38,8 +45,9 @@ export const validateCommand = defineCommand({
38
45
  },
39
46
  },
40
47
  meta: commandMeta.validate,
41
- async run({ args }) {
48
+ async run({ args, rawArgs }) {
42
49
  const root = process.cwd();
50
+ const ignore = parseIgnoreFlag(rawArgs);
43
51
  const diagnostics: Diagnostic[] = [];
44
52
 
45
53
  try {
@@ -83,6 +91,7 @@ export const validateCommand = defineCommand({
83
91
  checkExternal: Boolean(args.external),
84
92
  extraRoutes,
85
93
  i18n: project.config.i18n,
94
+ ignore,
86
95
  publicDir: existsSync(publicDir) ? publicDir : null,
87
96
  redirects: project.config.redirects,
88
97
  }))
@@ -557,6 +557,8 @@ const pnpmWorkspaceSchema = z.looseObject({
557
557
  // A glob's leading `./` and trailing `/`, which pnpm ignores.
558
558
  const GLOB_EDGES = /^\.\/|\/+$/gu;
559
559
 
560
+ const globPattern = (glob: string): string => glob.replaceAll(GLOB_EDGES, "");
561
+
560
562
  /** Whether any of `globs` matches the workspace-relative path `at`. */
561
563
  const matchesAny = (globs: string[], at: string): boolean =>
562
564
  globs.length > 0 && picomatch(globs)(at);
@@ -589,11 +591,12 @@ const listsPackage = (workspace: string, root: string): boolean => {
589
591
  return true;
590
592
  }
591
593
  const globs = parsed.data.packages;
592
- const pattern = (glob: string): string => glob.replaceAll(GLOB_EDGES, "");
593
- const included = globs.filter((glob) => !glob.startsWith("!")).map(pattern);
594
+ const included = globs
595
+ .filter((glob) => !glob.startsWith("!"))
596
+ .map(globPattern);
594
597
  const excluded = globs
595
598
  .filter((glob) => glob.startsWith("!"))
596
- .map((glob) => pattern(glob.slice(1)));
599
+ .map((glob) => globPattern(glob.slice(1)));
597
600
  return matchesAny(included, at) && !matchesAny(excluded, at);
598
601
  };
599
602
 
@@ -679,7 +682,7 @@ const ESBUILD_APPROVAL = " allowBuilds:\n esbuild: true";
679
682
  * What the project still needs from a workspace it sits in, when `init` left
680
683
  * that workspace's config alone: a place in a pnpm workspace's `packages`
681
684
  * (pnpm skips a folder it doesn't list), pnpm's approval for esbuild's build
682
- * script (pnpm 10 and later fail the install without it), or Yarn Berry's
685
+ * script (pnpm 11 and later fail the install without it), or Yarn Berry's
683
686
  * `node_modules` linker. Undefined when the workspace already has it, or the
684
687
  * project doesn't sit in one.
685
688
  */
@@ -699,12 +702,12 @@ export const workspaceNote = (
699
702
  const entry = ` packages:\n - ${relative(workspace, root)}`;
700
703
  return approved
701
704
  ? `This folder sits inside the pnpm workspace at ${file}, which doesn't list it under packages, so pnpm won't install it. Add it there before installing:\n\n${entry}`
702
- : `This folder sits inside the pnpm workspace at ${file}, which doesn't list it under packages, so pnpm won't install it. Add it there before installing, and approve esbuild's build script, or pnpm 10 and later stop the install:\n\n${entry}\n${ESBUILD_APPROVAL}`;
705
+ : `This folder sits inside the pnpm workspace at ${file}, which doesn't list it under packages, so pnpm won't install it. Add it there before installing, and approve esbuild's build script, or pnpm 11 and later stop the install:\n\n${entry}\n${ESBUILD_APPROVAL}`;
703
706
  }
704
707
  if (approved) {
705
708
  return;
706
709
  }
707
- return `This project joins the pnpm workspace at ${file}. Approve esbuild's build script there before installing, or pnpm 10 and later stop the install:\n\n${ESBUILD_APPROVAL}`;
710
+ return `This project joins the pnpm workspace at ${file}. Approve esbuild's build script there before installing, or pnpm 11 and later stop the install:\n\n${ESBUILD_APPROVAL}`;
708
711
  }
709
712
  if (answers.packageManager !== "yarn" || yarnMajor(env.userAgent) === 1) {
710
713
  return;
@@ -59,6 +59,12 @@ export const prepareProject = async (
59
59
  let project: BlumeProject;
60
60
  try {
61
61
  project = await scanProject(options.root, {
62
+ // Fail-fast on missing secrets: warn now, not at the first request, and
63
+ // before the sources fetch, so a source that can't authenticate on a
64
+ // fresh build still gets its variable named.
65
+ beforeSources: (config) => {
66
+ reportDiagnostics(checkRequiredSecrets(config), options.root);
67
+ },
62
68
  devServerUrl: options.devServerUrl,
63
69
  mode: options.mode,
64
70
  overrides: options.overrides,
@@ -138,8 +144,5 @@ export const prepareProject = async (
138
144
  logger.warn(warning);
139
145
  }
140
146
 
141
- // Fail-fast on missing runtime secrets: warn now, not at the first request.
142
- reportDiagnostics(checkRequiredSecrets(project.config), options.root);
143
-
144
147
  return project;
145
148
  };
@@ -61,7 +61,7 @@ const adapterSecretOwners = (config: ResolvedConfig): SecretOwner[] => {
61
61
  * the failure surfaces at `blume dev`/`build` instead of at the first request in
62
62
  * production. These are runtime secrets (the endpoint reads them on the server),
63
63
  * so this warns rather than hard-fails — the value may live only in the deploy
64
- * environment. Build-time secrets (search-index sync) already warn during sync.
64
+ * environment. Build-time secrets (search-index sync) are checked by the sync.
65
65
  */
66
66
  export const checkRequiredSecrets = (config: ResolvedConfig): Diagnostic[] => {
67
67
  const diagnostics: Diagnostic[] = [];
@@ -92,7 +92,8 @@ export const checkRequiredSecrets = (config: ResolvedConfig): Diagnostic[] => {
92
92
  }
93
93
 
94
94
  // Generated narration reads its key at build, not at runtime; without it the
95
- // build skips the audio and pages read with browser voices.
95
+ // build skips the audio and pages read with browser voices. `openai()` with
96
+ // a `baseUrl` requires only a key it names (see `narrationProviderSchema`).
96
97
  const { narration } = config;
97
98
  if (narration.enabled && narration.provider) {
98
99
  for (const env of narration.provider.requiredSecrets) {
@@ -11,7 +11,10 @@ import { operationModel } from "../openapi/operation-model.ts";
11
11
  import Playground from "../openapi/Playground.astro";
12
12
  import { buildRequest, defaultValues } from "../openapi/request.ts";
13
13
  import RequestPanel from "../openapi/RequestPanel.astro";
14
- import { sampleLanguages } from "../openapi/snippets.ts";
14
+ import {
15
+ DEFAULT_SAMPLE_LANGUAGES,
16
+ sampleLanguages,
17
+ } from "../openapi/snippets.ts";
15
18
  import {
16
19
  endpointBody,
17
20
  endpointParameters,
@@ -46,6 +49,7 @@ const tryIt = spec.playground !== "simple" && api.playground.enabled;
46
49
  model={model}
47
50
  operation={`${spec.method} ${spec.target}`}
48
51
  proxy={api.playground.proxy}
52
+ proxyConfig="api"
49
53
  slug="api-pages"
50
54
  />
51
55
  )
@@ -53,7 +57,7 @@ const tryIt = spec.playground !== "simple" && api.playground.enabled;
53
57
  {
54
58
  !spec.requestExample && (
55
59
  <RequestPanel
56
- languages={sampleLanguages([])}
60
+ languages={sampleLanguages(DEFAULT_SAMPLE_LANGUAGES)}
57
61
  responses={{}}
58
62
  sample={buildRequest(model, defaultValues(model))}
59
63
  schemas={{}}
@@ -43,7 +43,10 @@ export interface ParamFieldProps extends FieldProps {
43
43
  /** The name, for a field with no location attribute. */
44
44
  name?: string;
45
45
  path?: string;
46
- /** Placeholder text for the parameter's playground input. */
46
+ /**
47
+ * An example value: the one the playground and request samples start
48
+ * with, not an input's placeholder hint (see `fieldSchema`).
49
+ */
47
50
  placeholder?: string;
48
51
  query?: string;
49
52
  }
@@ -1,3 +1,4 @@
1
+ import { exampleValue } from "../openapi/helpers.ts";
1
2
  import type { ParameterLike, SchemaLike } from "../openapi/helpers.ts";
2
3
  import { resolveSecurity } from "../openapi/security.ts";
3
4
  import type { OperationSecurity } from "../openapi/security.ts";
@@ -168,19 +169,42 @@ export const endpointParameters = (fields: EndpointField[]): ParameterLike[] =>
168
169
  ]
169
170
  );
170
171
 
171
- /** The body fields as a JSON request body, or `undefined` when there are none. */
172
+ /** Whether a field, or one nested in it, has a `default` or `placeholder`. */
173
+ const hasValue = (field: EndpointField): boolean =>
174
+ field.default !== undefined ||
175
+ Boolean(field.placeholder) ||
176
+ (field.fields ?? []).some(hasValue);
177
+
178
+ /**
179
+ * The fields a request starts with: required ones, and ones with a value to
180
+ * send. An optional field with neither is left for the reader to fill,
181
+ * rather than sent as the sampler's `"string"`, `0`, or `true`.
182
+ */
183
+ const sentFields = (fields: EndpointField[]): EndpointField[] =>
184
+ fields
185
+ .filter((field) => field.required === true || hasValue(field))
186
+ .map((field) => ({ ...field, fields: sentFields(field.fields ?? []) }));
187
+
188
+ /**
189
+ * The body fields as a JSON request body, or `undefined` when there are none.
190
+ * Its example, which the playground and samples start from, holds only the
191
+ * {@link sentFields}.
192
+ */
172
193
  export const endpointBody = (fields: EndpointField[]) => {
173
194
  const body = fields.filter((field) => field.location === "body");
195
+ const object = (members: EndpointField[]): SchemaLike =>
196
+ fieldSchema({
197
+ fields: members,
198
+ location: "body",
199
+ name: "",
200
+ type: "object",
201
+ });
174
202
  return body.length > 0
175
203
  ? {
176
204
  content: {
177
205
  "application/json": {
178
- schema: fieldSchema({
179
- fields: body,
180
- location: "body",
181
- name: "",
182
- type: "object",
183
- }),
206
+ example: exampleValue(object(sentFields(body)), {}),
207
+ schema: object(body),
184
208
  },
185
209
  },
186
210
  }
@@ -55,6 +55,9 @@ export class BlumeMermaid extends HTMLElement {
55
55
  look: "classic",
56
56
  securityLevel: "strict",
57
57
  startOnLoad: false,
58
+ // Without this, a diagram that fails to parse leaves Mermaid's own
59
+ // "Syntax error in text" graphic appended to <body>, below the page.
60
+ suppressErrorRendering: true,
58
61
  theme: prefersDark() ? "dark" : "default",
59
62
  });
60
63
  try {
@@ -71,13 +74,24 @@ export class BlumeMermaid extends HTMLElement {
71
74
  // oxlint-disable-next-line github/no-inner-html -- Mermaid-generated SVG must be injected as HTML
72
75
  output.innerHTML = svg;
73
76
  }
74
- } catch {
77
+ } catch (error) {
75
78
  // Localized message stamped on <body> by RootLayout's markup (the
76
79
  // data-attribute channel); English fallback when the attribute is
77
80
  // missing (a stale snapshot or a custom layout).
78
81
  output.textContent =
79
82
  document.body.dataset.i18nDiagramError ||
80
83
  "Could not render this diagram.";
84
+ // Readers get only the message above; the writer needs Mermaid's
85
+ // parser error (line and expected token) to fix the source. It goes
86
+ // to the console, and `blume dev` also shows it under the message.
87
+ console.error("[blume] Mermaid could not render a diagram:", error);
88
+ if (import.meta.env.DEV) {
89
+ const detail = document.createElement("pre");
90
+ detail.className =
91
+ "mt-2 overflow-x-auto text-muted-foreground text-xs";
92
+ detail.textContent = String(error);
93
+ output.append(detail);
94
+ }
81
95
  }
82
96
  output.removeAttribute("aria-busy");
83
97
  };
@@ -42,6 +42,10 @@ const cssString = (value: string): string =>
42
42
  .replaceAll("<", String.raw`\3c `)
43
43
  .replaceAll(/[\n\r\f]/gu, " ")}"`;
44
44
 
45
+ /** Everything tagged with a view other than `title`. */
46
+ const otherViews = (title: string): string =>
47
+ `[data-blume-view]:not([data-blume-view=${cssString(title)}])`;
48
+
45
49
  /**
46
50
  * The page's view CSS: with a view chosen on `<body>`, everything tagged with
47
51
  * another is hidden; before a choice, everything but the first view is.
@@ -51,12 +55,11 @@ export const viewStyle = (titles: readonly string[]): string => {
51
55
  if (first === undefined) {
52
56
  return "";
53
57
  }
54
- const other = (title: string): string =>
55
- `[data-blume-view]:not([data-blume-view=${cssString(title)}])`;
56
58
  const selectors = [
57
- `body:not([data-blume-view]) ${other(first)}`,
59
+ `body:not([data-blume-view]) ${otherViews(first)}`,
58
60
  ...titles.map(
59
- (title) => `body[data-blume-view=${cssString(title)}] ${other(title)}`
61
+ (title) =>
62
+ `body[data-blume-view=${cssString(title)}] ${otherViews(title)}`
60
63
  ),
61
64
  ];
62
65
  return `${selectors.join(",")}{display:none!important}`;
@@ -1,5 +1,9 @@
1
1
  import { useCallback, useEffect, useRef, useState } from "react";
2
2
 
3
+ import {
4
+ ASK_MAX_MESSAGES,
5
+ ASK_MAX_MESSAGES_CHARS,
6
+ } from "../../ai/ask-limits.ts";
3
7
  import { captchaToken } from "../../captcha/client.ts";
4
8
  import type { CaptchaSettings } from "../../captcha/schema.ts";
5
9
  import type { BlumeClientData } from "../../core/data.ts";
@@ -264,6 +268,28 @@ const refusalNotice = (
264
268
  const currentPath = (): string =>
265
269
  stripBase(import.meta.env.BASE_URL, window.location.pathname);
266
270
 
271
+ /**
272
+ * The conversation as the route is sent it: all of it while it fits the
273
+ * route's limits (`ai/ask-limits.ts`), else the latest turns that do, the
274
+ * oldest dropped first, so a long chat keeps working instead of answering
275
+ * `400`. The kept turns open with a question, not an answer cut from its
276
+ * own, and the question being asked always goes, even alone over the limit.
277
+ */
278
+ const withinAskLimits = (history: AskMessage[]): AskMessage[] => {
279
+ const last = history.length - 1;
280
+ let start = Math.max(0, history.length - ASK_MAX_MESSAGES);
281
+ while (
282
+ start < last &&
283
+ JSON.stringify(history.slice(start)).length > ASK_MAX_MESSAGES_CHARS
284
+ ) {
285
+ start += 1;
286
+ }
287
+ while (start < last && history[start]?.role !== "user") {
288
+ start += 1;
289
+ }
290
+ return history.slice(start);
291
+ };
292
+
267
293
  /**
268
294
  * Stream answers from the assistant endpoint. Mirrors the built-in assistant island so
269
295
  * a custom chat UI shares the same grounded, page-aware backend.
@@ -366,7 +392,7 @@ export const useAssistant = (
366
392
  }
367
393
  const payload: AskPayload = {
368
394
  captcha: token,
369
- messages: history,
395
+ messages: withinAskLimits(history),
370
396
  page: { path },
371
397
  };
372
398
  const response = await fetch(endpoint, {
@@ -307,15 +307,59 @@ const initialId =
307
307
  const spacing =
308
308
  index === 0 ? "mt-0" : display === "flat" ? "mt-6" : "mt-0.5";
309
309
 
310
- // `page`: a single row that drills into this group's panel. It
311
- // controls that panel; NavTreeScript keeps `aria-expanded` in step
312
- // with the panel shown (static markup, since cached subtrees and
313
- // fragments can't carry per-page state).
310
+ // `page`: a single row that drills into this group's panel. Its drill
311
+ // button controls that panel; NavTreeScript keeps `aria-expanded` in
312
+ // step with the panel shown (static markup, since cached subtrees and
313
+ // fragments can't carry per-page state). A group with an index page
314
+ // splits the row, like the `group` summary: the label links that page
315
+ // (which renders with the panel open), so the row opens the section
316
+ // even when the index hides its own row, and the chevron stays a
317
+ // button that slides the panel in without leaving the page. Without
318
+ // one, the whole row is the drill button, so it never links a missing
319
+ // page. `aria-current` marks the row whose panel holds the current
320
+ // page (`page` on the section's own page), so the parent panel still
321
+ // shows where the reader is after the back arrow. Only rows on the
322
+ // active path carry it, and those always render live, never from a
323
+ // cache.
314
324
  if (display === "page") {
315
- return (
325
+ const current = hasActivePage(item)
326
+ ? isGroupRowCurrent(item, currentRoute)
327
+ ? "page"
328
+ : "true"
329
+ : undefined;
330
+ return item.route ? (
331
+ <li class={`flex items-center gap-0.5 ${spacing}`}>
332
+ <a
333
+ aria-current={current}
334
+ class="blume-nav-drill-link"
335
+ href={withMountedBase(item.route)}
336
+ >
337
+ {item.icon && (
338
+ <Icon
339
+ class="shrink-0 text-muted-foreground"
340
+ name={item.icon}
341
+ size={14}
342
+ />
343
+ )}
344
+ <span class="flex-1 truncate">{item.label}</span>
345
+ {item.badge && <span class={badgeClassFor(item.badge)}>{item.badge}</span>}
346
+ </a>
347
+ <button
348
+ aria-controls={navPanelId(id)}
349
+ aria-expanded="false"
350
+ aria-label={item.label}
351
+ class="blume-nav-drill-toggle"
352
+ data-nav-to={id}
353
+ type="button"
354
+ >
355
+ <Icon class="rtl:-scale-x-100" name="chevron-right" size={14} />
356
+ </button>
357
+ </li>
358
+ ) : (
316
359
  <li class={spacing}>
317
360
  <button
318
361
  aria-controls={navPanelId(id)}
362
+ aria-current={current}
319
363
  aria-expanded="false"
320
364
  class="blume-nav-drill"
321
365
  data-nav-to={id}
@@ -146,6 +146,24 @@
146
146
  }
147
147
  return;
148
148
  }
149
+ // After the back arrow, the current section's name links the page
150
+ // already on screen, so a plain click slides its panel back in rather
151
+ // than reloading that page. Modified clicks stay the browser's.
152
+ const own = target.closest('.blume-nav-drill-link[aria-current="page"]');
153
+ const ownId = own?.parentElement
154
+ ?.querySelector("[data-nav-to]")
155
+ ?.getAttribute("data-nav-to");
156
+ if (
157
+ own &&
158
+ ownId &&
159
+ this.contains(own) &&
160
+ this.panel(ownId) &&
161
+ !(event.metaKey || event.ctrlKey || event.shiftKey || event.altKey)
162
+ ) {
163
+ event.preventDefault();
164
+ void this.show(ownId, true);
165
+ return;
166
+ }
149
167
  const back = target.closest("[data-nav-back]");
150
168
  if (back && this.contains(back)) {
151
169
  event.preventDefault();