blume 2.0.1 → 2.0.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 (410) hide show
  1. package/CHANGELOG.md +82 -0
  2. package/dist/cli/{chunk-vtk4a6dg.js → chunk-00gs3wqs.js} +1 -1
  3. package/dist/cli/{chunk-fa25z98p.js → chunk-273ygyr4.js} +4 -3
  4. package/dist/cli/chunk-273ygyr4.js.map +11 -0
  5. package/dist/cli/{chunk-yw7dm696.js → chunk-3yce002v.js} +21 -8
  6. package/dist/cli/{chunk-yw7dm696.js.map → chunk-3yce002v.js.map} +3 -3
  7. package/dist/cli/{chunk-yt5n7ppj.js → chunk-4e9b9ra6.js} +17 -7
  8. package/dist/cli/chunk-4e9b9ra6.js.map +10 -0
  9. package/dist/cli/{chunk-fs23ddbb.js → chunk-5r8g91qn.js} +352 -676
  10. package/dist/cli/chunk-5r8g91qn.js.map +34 -0
  11. package/dist/cli/{chunk-6vm74dry.js → chunk-6dtt0zfn.js} +8 -8
  12. package/dist/cli/{chunk-qwsrynx5.js → chunk-6k4ftwze.js} +38 -19
  13. package/dist/cli/{chunk-qwsrynx5.js.map → chunk-6k4ftwze.js.map} +4 -4
  14. package/dist/cli/{chunk-qs4q5p4e.js → chunk-7mbqtmgb.js} +16 -7
  15. package/dist/cli/chunk-7mbqtmgb.js.map +10 -0
  16. package/dist/cli/{chunk-q5163e60.js → chunk-7vtckvaw.js} +21 -19
  17. package/dist/cli/chunk-7vtckvaw.js.map +11 -0
  18. package/dist/cli/{chunk-kdp5q7ke.js → chunk-8g8ytmgx.js} +17 -18
  19. package/dist/cli/{chunk-kdp5q7ke.js.map → chunk-8g8ytmgx.js.map} +2 -2
  20. package/dist/cli/{chunk-epjnccmv.js → chunk-91ws1n6j.js} +18 -15
  21. package/dist/cli/chunk-91ws1n6j.js.map +10 -0
  22. package/dist/cli/{chunk-fxypxtvm.js → chunk-bbnwccaz.js} +2 -2
  23. package/dist/cli/{chunk-m3vmjgmq.js → chunk-bfwp9vp6.js} +16 -8
  24. package/dist/cli/chunk-bfwp9vp6.js.map +10 -0
  25. package/dist/cli/{chunk-f2z5v128.js → chunk-d1v5rhy0.js} +14 -15
  26. package/dist/cli/{chunk-f2z5v128.js.map → chunk-d1v5rhy0.js.map} +2 -2
  27. package/dist/cli/{chunk-6crbhc3x.js → chunk-ddndchfr.js} +21 -6
  28. package/dist/cli/chunk-ddndchfr.js.map +14 -0
  29. package/dist/cli/{chunk-ce574jw2.js → chunk-esh98wmb.js} +1 -1
  30. package/dist/cli/{chunk-zxcczpyx.js → chunk-fsmrqk8a.js} +1 -1
  31. package/dist/cli/{chunk-hdpx1tax.js → chunk-g698a744.js} +5 -5
  32. package/dist/cli/{chunk-jts8mvcz.js → chunk-gs7r695n.js} +9 -3
  33. package/dist/cli/{chunk-jts8mvcz.js.map → chunk-gs7r695n.js.map} +3 -3
  34. package/dist/cli/{chunk-zxh4d9vy.js → chunk-h2ez8dzb.js} +4 -4
  35. package/dist/cli/{chunk-5shv93fd.js → chunk-h7k3nq3v.js} +2 -2
  36. package/dist/cli/{chunk-79jhk4py.js → chunk-hqp2ajnh.js} +251 -122
  37. package/dist/cli/chunk-hqp2ajnh.js.map +35 -0
  38. package/dist/cli/{chunk-2hn4b8z7.js → chunk-hr8ne106.js} +109 -40
  39. package/dist/cli/chunk-hr8ne106.js.map +13 -0
  40. package/dist/cli/{chunk-6hsn950k.js → chunk-j85scx15.js} +62 -18
  41. package/dist/cli/chunk-j85scx15.js.map +10 -0
  42. package/dist/cli/{chunk-s1p84fyh.js → chunk-k7pj68a8.js} +63 -22
  43. package/dist/cli/chunk-k7pj68a8.js.map +11 -0
  44. package/dist/cli/{chunk-ah61y8py.js → chunk-mqc662a6.js} +2 -2
  45. package/dist/cli/{chunk-ch6g3ar0.js → chunk-n1yg3tj3.js} +4 -4
  46. package/dist/cli/{chunk-mb2919y2.js → chunk-nfcyttvj.js} +17 -6
  47. package/dist/cli/chunk-nfcyttvj.js.map +10 -0
  48. package/dist/cli/{chunk-kpf8rrjc.js → chunk-pbg5a4s3.js} +56 -24
  49. package/dist/cli/chunk-pbg5a4s3.js.map +19 -0
  50. package/dist/cli/{chunk-jwyddg7y.js → chunk-ppzjqwx2.js} +21 -14
  51. package/dist/cli/{chunk-jwyddg7y.js.map → chunk-ppzjqwx2.js.map} +4 -4
  52. package/dist/cli/{chunk-dh8cwk36.js → chunk-qkb5a8sa.js} +24 -9
  53. package/dist/cli/chunk-qkb5a8sa.js.map +10 -0
  54. package/dist/cli/{chunk-wm7js3j9.js → chunk-sqw4ekg1.js} +2 -2
  55. package/dist/cli/{chunk-qkqwkpte.js → chunk-v6ya5kcb.js} +3083 -953
  56. package/dist/cli/chunk-v6ya5kcb.js.map +189 -0
  57. package/dist/cli/{chunk-fz5wtpmh.js → chunk-w4bxdvsa.js} +18 -15
  58. package/dist/cli/chunk-w4bxdvsa.js.map +10 -0
  59. package/dist/cli/{chunk-27g6wdth.js → chunk-wdrt2k2v.js} +2 -2
  60. package/dist/cli/{chunk-wgm7m9qk.js → chunk-xh43dwgw.js} +158 -43
  61. package/dist/cli/chunk-xh43dwgw.js.map +36 -0
  62. package/dist/cli/{chunk-s6jhgk0q.js → chunk-zp79m0ts.js} +2 -2
  63. package/dist/cli/index.js +160 -35
  64. package/dist/cli/index.js.map +4 -4
  65. package/dist/types/ai/agent-surface.d.ts +32 -0
  66. package/dist/types/ai/api-catalog.d.ts +7 -1
  67. package/dist/types/ai/ask-context.d.ts +7 -0
  68. package/dist/types/ai/component-markdown.d.ts +4 -4
  69. package/dist/types/ai/relative-links.d.ts +9 -4
  70. package/dist/types/ai/static-expression.d.ts +28 -0
  71. package/dist/types/analytics/databuddy.d.ts +43 -0
  72. package/dist/types/analytics/index.d.ts +2 -0
  73. package/dist/types/analytics/schema.d.ts +14 -0
  74. package/dist/types/cli/env.d.ts +5 -0
  75. package/dist/types/cli/init/scaffold.d.ts +19 -3
  76. package/dist/types/cli/init/starter-spec.d.ts +11 -0
  77. package/dist/types/core/base-path.d.ts +9 -0
  78. package/dist/types/core/config-input.d.ts +4 -4
  79. package/dist/types/core/config.d.ts +2 -2
  80. package/dist/types/core/graph.d.ts +2 -0
  81. package/dist/types/core/i18n-ui.d.ts +31 -0
  82. package/dist/types/core/i18n.d.ts +7 -1
  83. package/dist/types/core/links.d.ts +3 -1
  84. package/dist/types/core/load-module.d.ts +10 -0
  85. package/dist/types/core/locale-links.d.ts +12 -2
  86. package/dist/types/core/meta.d.ts +5 -1
  87. package/dist/types/core/nav-diagnostics.d.ts +10 -0
  88. package/dist/types/core/navigation.d.ts +38 -0
  89. package/dist/types/core/ordering-prefix.d.ts +4 -0
  90. package/dist/types/core/safe-links.d.ts +3 -1
  91. package/dist/types/core/schema.d.ts +52 -13
  92. package/dist/types/core/sources/github-releases.d.ts +5 -0
  93. package/dist/types/core/sources/lower.d.ts +14 -2
  94. package/dist/types/core/sources/normalize.d.ts +10 -1
  95. package/dist/types/core/sources/remote.d.ts +11 -1
  96. package/dist/types/core/sources/resolve.d.ts +12 -0
  97. package/dist/types/core/sources/types.d.ts +31 -0
  98. package/dist/types/core/types.d.ts +9 -0
  99. package/dist/types/deploy/adapters/node.d.ts +5 -2
  100. package/dist/types/deploy/adapters/types.d.ts +7 -0
  101. package/dist/types/deploy/cloudflare-negotiation.d.ts +3 -2
  102. package/dist/types/deploy/headers.d.ts +31 -7
  103. package/dist/types/deploy/node-headers.d.ts +43 -8
  104. package/dist/types/deploy/platforms/netlify.d.ts +27 -2
  105. package/dist/types/deploy/platforms/node.d.ts +6 -5
  106. package/dist/types/deploy/platforms/types.d.ts +7 -0
  107. package/dist/types/deploy/platforms/vercel.d.ts +3 -2
  108. package/dist/types/deploy/redirects.d.ts +7 -2
  109. package/dist/types/deploy/vercel-negotiation.d.ts +3 -2
  110. package/dist/types/openapi/model.d.ts +20 -6
  111. package/dist/types/search/sync/algolia.d.ts +3 -1
  112. package/docs/01-quickstart.mdx +3 -2
  113. package/docs/02-deployment.mdx +17 -8
  114. package/docs/08-faq.mdx +9 -2
  115. package/docs/advanced/changelog.mdx +1 -1
  116. package/docs/advanced/custom-pages.mdx +4 -2
  117. package/docs/cli/audit.mdx +2 -2
  118. package/docs/cli/doctor.mdx +2 -2
  119. package/docs/cli/evals.mdx +2 -2
  120. package/docs/cli/index.mdx +4 -1
  121. package/docs/cli/translate.mdx +1 -1
  122. package/docs/configuration/analytics.mdx +23 -2
  123. package/docs/configuration/assistant.mdx +1 -1
  124. package/docs/configuration/customization.mdx +4 -3
  125. package/docs/configuration/index.mdx +5 -3
  126. package/docs/configuration/search.mdx +1 -1
  127. package/docs/content/components.mdx +1 -1
  128. package/docs/content/frontmatter.mdx +5 -1
  129. package/docs/content/i18n.mdx +1 -1
  130. package/docs/content/index.mdx +4 -2
  131. package/docs/content/meta.mdx +5 -3
  132. package/docs/content/navigation.mdx +29 -4
  133. package/docs/content/sources.mdx +18 -12
  134. package/docs/content/versioning.mdx +1 -0
  135. package/docs/discoverability/agent-discovery.mdx +21 -7
  136. package/docs/discoverability/index.mdx +2 -2
  137. package/docs/discoverability/llms-txt.mdx +2 -5
  138. package/docs/discoverability/markdown.mdx +5 -3
  139. package/docs/discoverability/mcp.mdx +4 -0
  140. package/docs/discoverability/metadata.mdx +3 -2
  141. package/docs/discoverability/open-graph.mdx +6 -4
  142. package/docs/discoverability/rss.mdx +3 -1
  143. package/docs/references/asyncapi.mdx +1 -1
  144. package/docs/references/graphql.mdx +2 -2
  145. package/docs/references/openapi.mdx +3 -3
  146. package/package.json +1 -1
  147. package/skills/blume-migrate/SKILL.md +6 -6
  148. package/skills/blume-migrate/references/docusaurus.md +6 -6
  149. package/skills/blume-migrate/references/mintlify.md +3 -3
  150. package/skills/blume-migrate/references/monorepo.md +1 -1
  151. package/skills/blume-migrate/references/nextra.md +1 -1
  152. package/skills/blume-migrate/references/starlight.md +5 -5
  153. package/skills/blume-migrate/scripts/mintlify-codemod.mjs +2 -2
  154. package/skills/blume-update-docs/references/audit-checklist.md +1 -1
  155. package/src/ai/agent-surface.ts +56 -0
  156. package/src/ai/api/spec.ts +15 -2
  157. package/src/ai/api-catalog.ts +7 -1
  158. package/src/ai/ask-context.ts +14 -2
  159. package/src/ai/ask-data.ts +26 -12
  160. package/src/ai/component-markdown.ts +43 -37
  161. package/src/ai/link-headers.ts +8 -3
  162. package/src/ai/llms.ts +4 -3
  163. package/src/ai/markdown.ts +23 -11
  164. package/src/ai/mcp/server.ts +78 -7
  165. package/src/ai/relative-links.ts +78 -10
  166. package/src/ai/static-expression.ts +416 -0
  167. package/src/ai/visibility.ts +45 -14
  168. package/src/analytics/databuddy.ts +67 -0
  169. package/src/analytics/head.ts +4 -0
  170. package/src/analytics/index.ts +2 -0
  171. package/src/analytics/posthog.ts +21 -3
  172. package/src/analytics/schema.ts +2 -0
  173. package/src/astro/generate.ts +71 -27
  174. package/src/astro/templates.ts +61 -23
  175. package/src/audit/catalog.ts +2 -2
  176. package/src/audit/checks/assets.ts +25 -5
  177. package/src/audit/checks/content.ts +20 -2
  178. package/src/audit/checks/i18n.ts +46 -8
  179. package/src/audit/checks/indexability.ts +39 -19
  180. package/src/audit/checks/links.ts +14 -0
  181. package/src/audit/checks/llms.ts +6 -3
  182. package/src/audit/checks/network.ts +3 -1
  183. package/src/audit/checks/og-image.ts +10 -0
  184. package/src/audit/checks/robots.ts +6 -1
  185. package/src/audit/checks/sitemap.ts +56 -31
  186. package/src/audit/checks/social.ts +21 -2
  187. package/src/audit/crawl.ts +88 -15
  188. package/src/audit/report.ts +54 -17
  189. package/src/audit/run.ts +1 -0
  190. package/src/audit/types.ts +18 -0
  191. package/src/audit/url.ts +37 -6
  192. package/src/cli/build-failure.ts +50 -0
  193. package/src/cli/commands/audit.ts +7 -0
  194. package/src/cli/commands/build.ts +19 -5
  195. package/src/cli/commands/check.ts +2 -0
  196. package/src/cli/commands/eval.ts +1 -7
  197. package/src/cli/commands/init.ts +15 -5
  198. package/src/cli/commands/preview.ts +15 -0
  199. package/src/cli/commands/sync.ts +2 -0
  200. package/src/cli/commands/translate.ts +9 -6
  201. package/src/cli/commands/upgrade.ts +11 -0
  202. package/src/cli/eject-scripts.ts +32 -7
  203. package/src/cli/env.ts +12 -1
  204. package/src/cli/init/scaffold.ts +117 -11
  205. package/src/cli/init/starter-spec.ts +235 -0
  206. package/src/components/content/AccordionItem.astro +26 -22
  207. package/src/components/content/Badge.astro +2 -9
  208. package/src/components/content/Card.astro +2 -2
  209. package/src/components/content/Component.astro +9 -3
  210. package/src/components/content/Expandable.astro +5 -1
  211. package/src/components/content/Frame.astro +2 -7
  212. package/src/components/content/GithubInfo.astro +12 -2
  213. package/src/components/content/Prompt.astro +2 -7
  214. package/src/components/content/Tabs.astro +54 -5
  215. package/src/components/content/Tile.astro +1 -1
  216. package/src/components/content/Tooltip.astro +69 -7
  217. package/src/components/content/Tree.astro +7 -2
  218. package/src/components/content/TypeTable.astro +10 -5
  219. package/src/components/content/Update.astro +8 -2
  220. package/src/components/content/auto-type-table.ts +4 -1
  221. package/src/components/content/badge-color.ts +17 -0
  222. package/src/components/content/base-href.ts +18 -3
  223. package/src/components/content/inline-markdown.ts +27 -7
  224. package/src/components/copy-feedback.ts +35 -8
  225. package/src/components/islands/assistant.tsx +16 -2
  226. package/src/components/islands/hooks.ts +7 -2
  227. package/src/components/islands/webmcp.ts +12 -8
  228. package/src/components/layout/Banner.astro +23 -4
  229. package/src/components/layout/Header.astro +22 -9
  230. package/src/components/layout/Logo.astro +5 -0
  231. package/src/components/layout/NavSelector.astro +6 -1
  232. package/src/components/layout/NavTabMenu.astro +133 -0
  233. package/src/components/layout/NavTree.astro +15 -6
  234. package/src/components/layout/NavTreeCache.astro +5 -2
  235. package/src/components/layout/NavTreeScript.astro +45 -5
  236. package/src/components/layout/PageLayout.astro +22 -8
  237. package/src/components/layout/ReferenceLayout.astro +4 -1
  238. package/src/components/layout/RootLayout.astro +21 -8
  239. package/src/components/layout/Search.astro +5 -1
  240. package/src/components/layout/analytics-client.ts +2 -0
  241. package/src/components/openapi/GraphqlType.astro +11 -3
  242. package/src/components/openapi/MessageComposer.astro +1 -1
  243. package/src/components/openapi/Operation.astro +21 -5
  244. package/src/components/openapi/PanelTabs.astro +4 -1
  245. package/src/components/openapi/Playground.astro +8 -2
  246. package/src/components/openapi/RequestPanel.astro +9 -5
  247. package/src/components/openapi/SchemaProperty.astro +11 -33
  248. package/src/components/openapi/SchemaTable.astro +27 -52
  249. package/src/components/openapi/helpers.ts +90 -12
  250. package/src/components/openapi/message-composer.ts +8 -0
  251. package/src/components/openapi/message-model.ts +12 -2
  252. package/src/components/openapi/message.ts +4 -1
  253. package/src/components/openapi/operation-model.ts +40 -8
  254. package/src/components/openapi/panel.ts +29 -5
  255. package/src/components/openapi/playground-client.ts +37 -7
  256. package/src/components/openapi/playground-schema.ts +25 -6
  257. package/src/components/openapi/request.ts +2 -0
  258. package/src/components/openapi/schema-tree.ts +203 -0
  259. package/src/components/openapi/snippets.ts +30 -15
  260. package/src/components/openapi/validate-json.ts +1 -1
  261. package/src/core/base-path.ts +15 -0
  262. package/src/core/config-input.ts +4 -4
  263. package/src/core/config.ts +18 -4
  264. package/src/core/diagnostics.ts +217 -30
  265. package/src/core/graph.ts +120 -11
  266. package/src/core/i18n-ui.ts +39 -2
  267. package/src/core/i18n.ts +13 -2
  268. package/src/core/links.ts +7 -4
  269. package/src/core/load-module.ts +20 -0
  270. package/src/core/locale-links.ts +17 -17
  271. package/src/core/manifest.ts +3 -2
  272. package/src/core/meta.ts +69 -6
  273. package/src/core/nav-diagnostics.ts +56 -1
  274. package/src/core/navigation.ts +245 -76
  275. package/src/core/ordering-prefix.ts +27 -0
  276. package/src/core/project-graph.ts +23 -1
  277. package/src/core/safe-href.ts +53 -1
  278. package/src/core/safe-links.ts +11 -2
  279. package/src/core/schema.ts +70 -14
  280. package/src/core/sources/assets.ts +83 -41
  281. package/src/core/sources/contentful-rich-text.ts +3 -2
  282. package/src/core/sources/contentful.ts +25 -12
  283. package/src/core/sources/filesystem.ts +5 -1
  284. package/src/core/sources/github-releases.ts +106 -5
  285. package/src/core/sources/lexical.ts +9 -4
  286. package/src/core/sources/lower.ts +79 -26
  287. package/src/core/sources/mdx-remote.ts +1 -0
  288. package/src/core/sources/normalize.ts +132 -30
  289. package/src/core/sources/notion.ts +26 -10
  290. package/src/core/sources/obsidian.ts +17 -3
  291. package/src/core/sources/payload.ts +1 -0
  292. package/src/core/sources/portable-text.ts +63 -44
  293. package/src/core/sources/remote.ts +18 -2
  294. package/src/core/sources/resolve.ts +52 -33
  295. package/src/core/sources/sanity.ts +1 -0
  296. package/src/core/sources/strapi-blocks.ts +9 -4
  297. package/src/core/sources/strapi.ts +1 -0
  298. package/src/core/sources/types.ts +31 -0
  299. package/src/core/types.ts +9 -0
  300. package/src/core/ui-packs/ar.ts +13 -0
  301. package/src/core/ui-packs/bg.ts +13 -0
  302. package/src/core/ui-packs/bn.ts +13 -0
  303. package/src/core/ui-packs/ca.ts +13 -0
  304. package/src/core/ui-packs/cs.ts +13 -0
  305. package/src/core/ui-packs/da.ts +13 -0
  306. package/src/core/ui-packs/de.ts +13 -0
  307. package/src/core/ui-packs/el.ts +13 -0
  308. package/src/core/ui-packs/es.ts +13 -0
  309. package/src/core/ui-packs/fa.ts +13 -0
  310. package/src/core/ui-packs/fi.ts +13 -0
  311. package/src/core/ui-packs/fr.ts +13 -0
  312. package/src/core/ui-packs/he.ts +13 -0
  313. package/src/core/ui-packs/hi.ts +13 -0
  314. package/src/core/ui-packs/hr.ts +13 -0
  315. package/src/core/ui-packs/hu.ts +13 -0
  316. package/src/core/ui-packs/id.ts +13 -0
  317. package/src/core/ui-packs/it.ts +13 -0
  318. package/src/core/ui-packs/ja.ts +13 -0
  319. package/src/core/ui-packs/ko.ts +13 -0
  320. package/src/core/ui-packs/nl.ts +13 -0
  321. package/src/core/ui-packs/no.ts +13 -0
  322. package/src/core/ui-packs/pl.ts +13 -0
  323. package/src/core/ui-packs/pt-br.ts +13 -0
  324. package/src/core/ui-packs/pt.ts +13 -0
  325. package/src/core/ui-packs/ro.ts +13 -0
  326. package/src/core/ui-packs/ru.ts +13 -0
  327. package/src/core/ui-packs/sk.ts +13 -0
  328. package/src/core/ui-packs/sr.ts +13 -0
  329. package/src/core/ui-packs/sv.ts +13 -0
  330. package/src/core/ui-packs/th.ts +13 -0
  331. package/src/core/ui-packs/tr.ts +13 -0
  332. package/src/core/ui-packs/uk.ts +13 -0
  333. package/src/core/ui-packs/vi.ts +13 -0
  334. package/src/core/ui-packs/zh-tw.ts +13 -0
  335. package/src/core/ui-packs/zh.ts +13 -0
  336. package/src/core/version-cut.ts +17 -2
  337. package/src/core/versions.ts +4 -1
  338. package/src/deploy/adapters/node.ts +5 -2
  339. package/src/deploy/adapters/registry.ts +2 -1
  340. package/src/deploy/adapters/types.ts +13 -1
  341. package/src/deploy/artifacts.ts +25 -5
  342. package/src/deploy/cloudflare-negotiation.ts +23 -4
  343. package/src/deploy/headers.ts +67 -51
  344. package/src/deploy/node-headers.ts +148 -27
  345. package/src/deploy/platforms/cloudflare.ts +1 -0
  346. package/src/deploy/platforms/netlify.ts +82 -5
  347. package/src/deploy/platforms/node.ts +7 -5
  348. package/src/deploy/platforms/static.ts +1 -0
  349. package/src/deploy/platforms/types.ts +7 -0
  350. package/src/deploy/platforms/vercel.ts +9 -3
  351. package/src/deploy/redirects.ts +14 -3
  352. package/src/deploy/vercel-negotiation.ts +35 -3
  353. package/src/eval/agents.ts +10 -2
  354. package/src/eval/run.ts +25 -0
  355. package/src/markdown/base-links.ts +55 -8
  356. package/src/markdown/index.ts +6 -3
  357. package/src/markdown/relative-links.ts +3 -23
  358. package/src/markdown/route-snapshot.ts +37 -0
  359. package/src/og/card.ts +20 -4
  360. package/src/og/derive.ts +145 -4
  361. package/src/openapi/graphql-build.ts +28 -2
  362. package/src/openapi/model.ts +77 -13
  363. package/src/openapi/render-mdx.ts +10 -1
  364. package/src/registry/eject.ts +119 -22
  365. package/src/search/documents.ts +22 -1
  366. package/src/search/sync/algolia.ts +36 -2
  367. package/src/sources/registry.ts +5 -0
  368. package/src/theme/entry.ts +11 -4
  369. package/src/translate/agents.ts +6 -1
  370. package/src/translate/ledger.ts +26 -3
  371. package/src/translate/meta.ts +11 -3
  372. package/src/translate/run.ts +11 -5
  373. package/src/translate/validate.ts +10 -1
  374. package/src/translate/work-list.ts +36 -3
  375. package/src/upgrade/upgrade.ts +36 -4
  376. package/dist/cli/chunk-2hn4b8z7.js.map +0 -12
  377. package/dist/cli/chunk-6crbhc3x.js.map +0 -14
  378. package/dist/cli/chunk-6hsn950k.js.map +0 -10
  379. package/dist/cli/chunk-79jhk4py.js.map +0 -35
  380. package/dist/cli/chunk-82bbrxdn.js +0 -51
  381. package/dist/cli/chunk-82bbrxdn.js.map +0 -10
  382. package/dist/cli/chunk-abh8yjkn.js +0 -31
  383. package/dist/cli/chunk-abh8yjkn.js.map +0 -10
  384. package/dist/cli/chunk-dh8cwk36.js.map +0 -10
  385. package/dist/cli/chunk-epjnccmv.js.map +0 -10
  386. package/dist/cli/chunk-fa25z98p.js.map +0 -11
  387. package/dist/cli/chunk-fs23ddbb.js.map +0 -35
  388. package/dist/cli/chunk-fz5wtpmh.js.map +0 -10
  389. package/dist/cli/chunk-kpf8rrjc.js.map +0 -19
  390. package/dist/cli/chunk-m3vmjgmq.js.map +0 -10
  391. package/dist/cli/chunk-mb2919y2.js.map +0 -10
  392. package/dist/cli/chunk-q5163e60.js.map +0 -11
  393. package/dist/cli/chunk-qkqwkpte.js.map +0 -182
  394. package/dist/cli/chunk-qs4q5p4e.js.map +0 -10
  395. package/dist/cli/chunk-s1p84fyh.js.map +0 -10
  396. package/dist/cli/chunk-wgm7m9qk.js.map +0 -36
  397. package/dist/cli/chunk-yt5n7ppj.js.map +0 -10
  398. /package/dist/cli/{chunk-vtk4a6dg.js.map → chunk-00gs3wqs.js.map} +0 -0
  399. /package/dist/cli/{chunk-6vm74dry.js.map → chunk-6dtt0zfn.js.map} +0 -0
  400. /package/dist/cli/{chunk-fxypxtvm.js.map → chunk-bbnwccaz.js.map} +0 -0
  401. /package/dist/cli/{chunk-ce574jw2.js.map → chunk-esh98wmb.js.map} +0 -0
  402. /package/dist/cli/{chunk-zxcczpyx.js.map → chunk-fsmrqk8a.js.map} +0 -0
  403. /package/dist/cli/{chunk-hdpx1tax.js.map → chunk-g698a744.js.map} +0 -0
  404. /package/dist/cli/{chunk-zxh4d9vy.js.map → chunk-h2ez8dzb.js.map} +0 -0
  405. /package/dist/cli/{chunk-5shv93fd.js.map → chunk-h7k3nq3v.js.map} +0 -0
  406. /package/dist/cli/{chunk-ah61y8py.js.map → chunk-mqc662a6.js.map} +0 -0
  407. /package/dist/cli/{chunk-ch6g3ar0.js.map → chunk-n1yg3tj3.js.map} +0 -0
  408. /package/dist/cli/{chunk-wm7js3j9.js.map → chunk-sqw4ekg1.js.map} +0 -0
  409. /package/dist/cli/{chunk-27g6wdth.js.map → chunk-wdrt2k2v.js.map} +0 -0
  410. /package/dist/cli/{chunk-s6jhgk0q.js.map → chunk-zp79m0ts.js.map} +0 -0
@@ -67,6 +67,7 @@ const kbd = "rounded border border-border bg-muted px-1 py-0.5 font-mono";
67
67
  data-i18n-dev={s.devOnly}
68
68
  data-i18n-empty={s.noResults}
69
69
  data-i18n-error={s.error}
70
+ data-i18n-latest={s.latest}
70
71
  data-i18n-popular={s.popular}
71
72
  data-i18n-results={s.results}
72
73
  data-locale={locale || undefined}
@@ -328,6 +329,7 @@ const kbd = "rounded border border-border bg-muted px-1 py-0.5 font-mono";
328
329
  allMsg = "All";
329
330
  popularMsg = "Popular";
330
331
  resultsMsg = "Results";
332
+ latestMsg = "latest";
331
333
  // The active locale to filter to (null when i18n is off), and whether the
332
334
  // reader has opted to search across every language instead.
333
335
  locale: string | null = null;
@@ -382,6 +384,8 @@ const kbd = "rounded border border-border bg-muted px-1 py-0.5 font-mono";
382
384
  this.getAttribute("data-i18n-popular") || this.popularMsg;
383
385
  this.resultsMsg =
384
386
  this.getAttribute("data-i18n-results") || this.resultsMsg;
387
+ this.latestMsg =
388
+ this.getAttribute("data-i18n-latest") || this.latestMsg;
385
389
  this.locale = this.getAttribute("data-locale");
386
390
  // "" (the current docs) is a real version value, so a presence flag —
387
391
  // not the attribute's truthiness — decides whether filtering is on.
@@ -765,7 +769,7 @@ const kbd = "rounded border border-border bg-muted px-1 py-0.5 font-mono";
765
769
  // reader knows they're about to leave the docs they're viewing.
766
770
  const versionTag =
767
771
  this.versioned && hit.version !== undefined && hit.version !== this.version
768
- ? `<span class="ms-2 inline-block rounded-full bg-muted px-1.5 py-0.5 align-middle text-[0.65rem] text-muted-foreground">${escapeHtml(hit.version || "latest")}</span>`
772
+ ? `<span class="ms-2 inline-block rounded-full bg-muted px-1.5 py-0.5 align-middle text-[0.65rem] text-muted-foreground">${escapeHtml(hit.version || this.latestMsg)}</span>`
769
773
  : "";
770
774
  el.innerHTML = `
771
775
  <span class="mt-0.5 shrink-0 text-muted-foreground">${svg("file")}</span>
@@ -29,6 +29,7 @@ interface AnalyticsWindow {
29
29
  analytics?: { track?: (event: string, props?: TrackProps) => void };
30
30
  clarity?: (command: "event", event: string) => void;
31
31
  dataLayer?: DataLayer;
32
+ databuddy?: { track?: (event: string, props?: TrackProps) => void };
32
33
  fathom?: { trackEvent?: (event: string) => void };
33
34
  gtag?: (command: "event", event: string, props?: TrackProps) => void;
34
35
  heap?: { track?: (event: string, props?: TrackProps) => void };
@@ -82,6 +83,7 @@ const trackGlobals = (
82
83
  attempt(() => (w.__blumeGtmLayer ?? w.dataLayer)?.push({ ...props, event }));
83
84
  // Privacy-first counters take a name, some with properties.
84
85
  attempt(() => w.plausible?.(event, { props }));
86
+ attempt(() => w.databuddy?.track?.(event, props));
85
87
  attempt(() => w.fathom?.trackEvent?.(event));
86
88
  // Pirsch stringifies `meta` values in place, so it gets its own copy and
87
89
  // the `blume:track` listeners still see the original types.
@@ -1,4 +1,5 @@
1
1
  ---
2
+ import { isSafeHref } from "../../core/safe-href.ts";
2
3
  import type { GraphqlDocument } from "../../openapi/graphql.ts";
3
4
  import { graphqlTypeDef } from "../../openapi/graphql.ts";
4
5
  import type { ApiOperationRef, ApiSpecData } from "../../openapi/model.ts";
@@ -65,9 +66,16 @@ const SECTION_HEADING = "mb-2 font-semibold text-foreground text-sm";
65
66
  {type.specifiedByUrl && (
66
67
  <div class="mb-4 text-muted-foreground text-sm">
67
68
  Specified by{" "}
68
- <a class="text-accent hover:underline" href={type.specifiedByUrl}>
69
- {type.specifiedByUrl}
70
- </a>
69
+ {/* The URL comes from the schema, someone else's content: a
70
+ `javascript:` one would run on the docs origin when clicked, so
71
+ only a safe destination becomes a link. */}
72
+ {isSafeHref(type.specifiedByUrl) ? (
73
+ <a class="text-accent hover:underline" href={type.specifiedByUrl}>
74
+ {type.specifiedByUrl}
75
+ </a>
76
+ ) : (
77
+ <code class="break-all">{type.specifiedByUrl}</code>
78
+ )}
71
79
  </div>
72
80
  )}
73
81
  <GraphqlFieldsTable
@@ -133,7 +133,7 @@ const BUTTON_QUIET =
133
133
  Payload
134
134
  {
135
135
  model.payload.contentType && (
136
- <span class="ml-1.5 font-mono normal-case">
136
+ <span class="ms-1.5 font-mono normal-case">
137
137
  {model.payload.contentType}
138
138
  </span>
139
139
  )
@@ -6,8 +6,13 @@ import {
6
6
  type ParameterLike,
7
7
  resolveComponentRef,
8
8
  type SchemaLike,
9
+ type SpecValue,
9
10
  } from "./helpers.ts";
10
- import { operationModel } from "./operation-model.ts";
11
+ import {
12
+ effectiveServers,
13
+ operationModel,
14
+ type ServerLike,
15
+ } from "./operation-model.ts";
11
16
  import { buildRequest, defaultValues } from "./request.ts";
12
17
  import {
13
18
  effectiveSecurity,
@@ -35,7 +40,8 @@ interface Props {
35
40
 
36
41
  interface MediaTypeLike {
37
42
  schema?: SchemaLike;
38
- example?: unknown;
43
+ example?: SpecValue;
44
+ examples?: SpecValue;
39
45
  }
40
46
 
41
47
  interface RequestBodyLike {
@@ -59,6 +65,7 @@ interface FullOperation {
59
65
  requestBody?: RequestBodyLike;
60
66
  responses?: Record<string, ResponseLike>;
61
67
  security?: SecurityRequirementLike[];
68
+ servers?: ServerLike[];
62
69
  }
63
70
 
64
71
  const { source, id } = Astro.props;
@@ -73,7 +80,10 @@ const isGraphql = spec?.kind === "graphql";
73
80
  const doc = (spec?.document ?? {}) as {
74
81
  paths?: Record<
75
82
  string,
76
- Record<string, unknown> & { parameters?: ParameterLike[] }
83
+ Record<string, unknown> & {
84
+ parameters?: ParameterLike[];
85
+ servers?: ServerLike[];
86
+ }
77
87
  >;
78
88
  components?: {
79
89
  schemas?: Record<string, SchemaLike>;
@@ -83,7 +93,7 @@ const doc = (spec?.document ?? {}) as {
83
93
  securitySchemes?: Record<string, SecuritySchemeLike>;
84
94
  };
85
95
  security?: SecurityRequirementLike[];
86
- servers?: { url?: string }[];
96
+ servers?: ServerLike[];
87
97
  };
88
98
  const pathItem = ref ? doc.paths?.[ref.path] : undefined;
89
99
  const operation = (
@@ -123,7 +133,13 @@ const model =
123
133
  requestBody,
124
134
  schemas,
125
135
  security,
126
- servers: doc.servers ?? [],
136
+ // An operation's own `servers` override its path item's, which
137
+ // override the document's.
138
+ servers: effectiveServers(
139
+ operation.servers,
140
+ pathItem?.servers,
141
+ doc.servers
142
+ ),
127
143
  })
128
144
  : null;
129
145
  const sample = model ? buildRequest(model, defaultValues(model)) : null;
@@ -62,7 +62,9 @@ const TAB_CLASS =
62
62
  </div>
63
63
  <blume-panel-tabs class="block overflow-hidden rounded-blume border border-border bg-background">
64
64
  <div class="flex items-center justify-between gap-2 border-border border-b px-3">
65
- <div class="flex flex-wrap gap-4" role="tablist">
65
+ <div aria-label={heading} class="flex flex-wrap gap-4" role="tablist">
66
+ {/* Roving tabindex: only the selected tab is in the Tab order, and
67
+ the arrow keys, Home, and End move between tabs (panel.ts). */}
66
68
  {panels.map((panel, index) => (
67
69
  <button
68
70
  aria-controls={idFor("panel", panel.key)}
@@ -71,6 +73,7 @@ const TAB_CLASS =
71
73
  data-panel-tab={panel.key}
72
74
  id={idFor("tab", panel.key)}
73
75
  role="tab"
76
+ tabindex={index === 0 ? "0" : "-1"}
74
77
  type="button"
75
78
  >
76
79
  {panel.label}
@@ -1,5 +1,6 @@
1
1
  ---
2
2
  import { stableId } from "../content/tooltip-id.ts";
3
+ import { withBase } from "../islands/base-path.ts";
3
4
 
4
5
  import { paramKey, type PlaygroundModel } from "./request.ts";
5
6
 
@@ -23,6 +24,11 @@ interface Props {
23
24
 
24
25
  const { model, proxy, slug, operation } = Astro.props;
25
26
 
27
+ // The built-in proxy route carries the site `basePath` but, like every
28
+ // internal URL in the spec data, not `deployment.base`; `withBase` adds it
29
+ // and passes an external proxy URL through untouched.
30
+ const proxyUrl = proxy ? withBase(proxy) : undefined;
31
+
26
32
  // `</script>`-proof embedding: `<` only ever occurs inside JSON strings,
27
33
  // where the \u escape is equivalent, so a spec description can never close
28
34
  // the model script early and inject markup.
@@ -46,7 +52,7 @@ const CHECKBOX_ROW =
46
52
 
47
53
  <blume-playground
48
54
  class="mb-6 block"
49
- data-proxy={proxy || undefined}
55
+ data-proxy={proxyUrl}
50
56
  data-storage-key={`blume-playground:${slug}:${operation}`}
51
57
  >
52
58
  <script
@@ -194,7 +200,7 @@ const CHECKBOX_ROW =
194
200
  <div class={GROUP}>
195
201
  <div class={GROUP_HEADING}>
196
202
  Body
197
- <span class="ml-1.5 font-mono normal-case">
203
+ <span class="ms-1.5 font-mono normal-case">
198
204
  {model.body.contentType}
199
205
  </span>
200
206
  </div>
@@ -2,14 +2,20 @@
2
2
  import data from "blume:data";
3
3
 
4
4
  import { highlightCode } from "../../markdown/index.ts";
5
- import { exampleValue, type SchemaLike, toJson } from "./helpers.ts";
5
+ import {
6
+ responseExample,
7
+ type SchemaLike,
8
+ type SpecValue,
9
+ toJson,
10
+ } from "./helpers.ts";
6
11
  import { languageSamplePanels } from "./sample-panels.ts";
7
12
  import PanelTabs from "./PanelTabs.astro";
8
13
  import type { RequestSample, SampleLanguage } from "./snippets.ts";
9
14
 
10
15
  interface MediaTypeLike {
11
16
  schema?: SchemaLike;
12
- example?: unknown;
17
+ example?: SpecValue;
18
+ examples?: SpecValue;
13
19
  }
14
20
 
15
21
  interface ResponseLike {
@@ -38,9 +44,7 @@ const responsePanels = await Promise.all(
38
44
  Object.entries(response.content ?? {}).find(([type]) =>
39
45
  type.includes("json")
40
46
  )?.[1] ?? Object.values(response.content ?? {})[0];
41
- const example = media
42
- ? (media.example ?? exampleValue(media.schema, schemas))
43
- : undefined;
47
+ const example = media ? responseExample(media, schemas) : undefined;
44
48
  const html =
45
49
  example === undefined || example === null
46
50
  ? null
@@ -3,33 +3,23 @@ import Description from "./Description.astro";
3
3
  import {
4
4
  constraints,
5
5
  isNullable,
6
- refName,
7
6
  resolveSchema,
8
7
  type SchemaLike,
9
8
  typeLabel,
10
9
  } from "./helpers.ts";
10
+ import { rowExpands, type SchemaRow } from "./schema-tree.ts";
11
11
  import SchemaTable from "./SchemaTable.astro";
12
12
 
13
13
  interface Props {
14
- name: string;
15
- schema: SchemaLike;
16
- required?: boolean;
14
+ /** One planned row of the parent table (see `schema-tree.ts`). */
15
+ row: SchemaRow;
17
16
  schemas: Record<string, SchemaLike>;
18
- seen?: string[];
19
17
  expandAll?: boolean;
20
18
  }
21
19
 
22
- const {
23
- name,
24
- schema,
25
- required = false,
26
- schemas,
27
- seen = [],
28
- expandAll = false,
29
- } = Astro.props;
20
+ const { row, schemas, expandAll = false } = Astro.props;
21
+ const { name, schema, required } = row;
30
22
 
31
- const refLabel = typeof schema.$ref === "string" ? refName(schema.$ref) : null;
32
- const circular = refLabel !== null && seen.includes(refLabel);
33
23
  const resolved = resolveSchema(schemas, schema);
34
24
 
35
25
  const type = typeLabel(schema);
@@ -39,20 +29,9 @@ const nullable = isNullable(resolved);
39
29
  const enumValues = Array.isArray(resolved.enum) ? resolved.enum : null;
40
30
  const limits = constraints(resolved);
41
31
 
42
- const types = Array.isArray(resolved.type) ? resolved.type : [resolved.type];
43
- const isArray = types.includes("array");
44
- const items = isArray ? resolveSchema(schemas, resolved.items) : null;
45
-
46
- const hasObjectShape = (candidate: SchemaLike): boolean =>
47
- Boolean(
48
- candidate.properties ||
49
- candidate.allOf ||
50
- candidate.oneOf ||
51
- candidate.anyOf ||
52
- typeof candidate.$ref === "string"
53
- );
54
- const expandable =
55
- !circular && (hasObjectShape(resolved) || Boolean(items && hasObjectShape(items)));
32
+ // Circles and schemas expanded elsewhere in the tree have no disclosure; the
33
+ // type label names them.
34
+ const children = rowExpands(row) ? row.children : undefined;
56
35
  ---
57
36
 
58
37
  {/* The row's vertical scale is 8 / 12 / 16px, and every other reference table copies it. A
@@ -103,17 +82,16 @@ const expandable =
103
82
  )
104
83
  }
105
84
  {
106
- expandable && (
85
+ children && (
107
86
  <details class="mt-3" open={expandAll}>
108
87
  <summary class="cursor-pointer select-none text-accent text-xs hover:underline">
109
88
  <span class="[details[open]>summary_&]:hidden">Show properties</span>
110
89
  <span class="hidden [details[open]>summary_&]:inline">Hide properties</span>
111
90
  </summary>
112
- <div class="mt-3 border-border border-l pl-4">
91
+ <div class="mt-3 border-border border-s ps-4">
113
92
  <SchemaTable
114
- schema={schema}
93
+ node={children.node}
115
94
  schemas={schemas}
116
- seen={seen}
117
95
  expandAll={expandAll}
118
96
  />
119
97
  </div>
@@ -1,86 +1,61 @@
1
1
  ---
2
- import {
3
- objectProperties,
4
- refName,
5
- resolveSchema,
6
- type SchemaLike,
7
- typeLabel,
8
- } from "./helpers.ts";
2
+ import type { SchemaLike } from "./helpers.ts";
3
+ import { type SchemaNode, schemaTree } from "./schema-tree.ts";
9
4
  import SchemaProperty from "./SchemaProperty.astro";
10
5
 
11
6
  interface Props {
12
- schema: SchemaLike;
7
+ /** The schema to render, planned here when no `node` is given. */
8
+ schema?: SchemaLike;
9
+ /** An already-planned subtree: how nested tables render. */
10
+ node?: SchemaNode;
13
11
  schemas: Record<string, SchemaLike>;
14
- /** `$ref` names already on the current branch, to break circular schemas. */
15
- seen?: string[];
16
12
  expandAll?: boolean;
17
13
  }
18
14
 
19
- const { schema, schemas, seen = [], expandAll = false } = Astro.props;
15
+ const { schema = {}, node: planned, schemas, expandAll = false } = Astro.props;
20
16
 
21
- // Track the ref so a self-referential model stops instead of recursing forever.
22
- const refLabel =
23
- typeof schema.$ref === "string" ? refName(schema.$ref) : null;
24
- const circular = refLabel !== null && seen.includes(refLabel);
25
- const nextSeen = refLabel ? [...seen, refLabel] : seen;
26
- const resolved = circular ? schema : resolveSchema(schemas, schema);
27
-
28
- const types = Array.isArray(resolved.type) ? resolved.type : [resolved.type];
29
- const isArray = types.includes("array");
30
- // Keep array items unresolved so a self-referential item `$ref` stays trackable
31
- // via `seen` — resolving here would drop the name and loop forever.
32
- const items = isArray ? (resolved.items ?? null) : null;
33
- const branches = resolved.oneOf ?? resolved.anyOf ?? null;
34
-
35
- const { properties, required } = circular
36
- ? { properties: [] as [string, SchemaLike][], required: new Set<string>() }
37
- : objectProperties(resolved, schemas);
17
+ // The whole tree is planned once, at the top-level table, so that which named
18
+ // schemas expand is decided for the tree as a whole (see `schema-tree.ts`).
19
+ const node = planned ?? schemaTree(schema, schemas);
38
20
  ---
39
21
 
40
22
  {
41
- circular ? (
23
+ node.kind === "circular" ? (
42
24
  <div class="text-muted-foreground text-sm">
43
- Circular reference to <code class="text-foreground">{refLabel}</code>.
25
+ Circular reference to <code class="text-foreground">{node.name}</code>.
44
26
  </div>
45
- ) : isArray ? (
27
+ ) : node.kind === "array" ? (
46
28
  <div>
47
29
  <div class="mb-2 text-muted-foreground text-xs">
48
- Array of <code class="text-foreground">{typeLabel(items ?? {})}</code>
30
+ Array of <code class="text-foreground">{node.itemsLabel}</code>
49
31
  </div>
50
- {items && (
51
- <Astro.self schema={items} schemas={schemas} seen={nextSeen} expandAll={expandAll} />
32
+ {node.items && (
33
+ <Astro.self node={node.items.node} schemas={schemas} expandAll={expandAll} />
52
34
  )}
53
35
  </div>
54
- ) : branches ? (
36
+ ) : node.kind === "branches" ? (
55
37
  <div class="flex flex-col gap-3">
56
- <div class="text-muted-foreground text-xs">
57
- {resolved.oneOf ? "One of" : "Any of"}:
58
- </div>
59
- {branches.map((branch, index) => (
38
+ <div class="text-muted-foreground text-xs">{node.label}:</div>
39
+ {node.branches.map((branch) => (
60
40
  <div class="rounded-blume border border-border p-3">
61
41
  <div class="mb-2 font-medium text-foreground text-xs">
62
- {typeLabel(branch) || `Option ${index + 1}`}
42
+ {branch.label}
63
43
  </div>
64
- <Astro.self schema={branch} schemas={schemas} seen={nextSeen} expandAll={expandAll} />
44
+ <Astro.self node={branch.slot.node} schemas={schemas} expandAll={expandAll} />
65
45
  </div>
66
46
  ))}
67
47
  </div>
68
- ) : properties.length > 0 ? (
48
+ ) : node.kind === "properties" ? (
69
49
  <div class="not-prose">
70
- {properties.map(([name, prop]) => (
71
- <SchemaProperty
72
- name={name}
73
- schema={prop}
74
- required={required.has(name)}
75
- schemas={schemas}
76
- seen={nextSeen}
77
- expandAll={expandAll}
78
- />
50
+ {node.rows.map((row) => (
51
+ <SchemaProperty row={row} schemas={schemas} expandAll={expandAll} />
79
52
  ))}
80
53
  </div>
81
54
  ) : (
82
55
  <div class="text-muted-foreground text-sm">
83
- <code class="text-foreground">{typeLabel(resolved)}</code>
56
+ <code class="text-foreground">
57
+ {node.kind === "named" ? node.name : node.label}
58
+ </code>
84
59
  </div>
85
60
  )
86
61
  }
@@ -152,17 +152,31 @@ export const resolveSchema = (
152
152
  return schema;
153
153
  };
154
154
 
155
- const nonNullTypes = (type: string | string[] | undefined): string[] => {
155
+ const declaredTypeList = (type: string | string[] | undefined): string[] => {
156
156
  if (!type) {
157
157
  return [];
158
158
  }
159
- return (Array.isArray(type) ? type : [type]).filter((t) => t !== "null");
159
+ return Array.isArray(type) ? type : [type];
160
+ };
161
+
162
+ const nonNullTypes = (type: string | string[] | undefined): string[] =>
163
+ declaredTypeList(type).filter((t) => t !== "null");
164
+
165
+ /**
166
+ * Whether a schema admits only `null`: `{ type: "null" }`, the branch Scalar's
167
+ * upgrade pairs with a 3.0 `nullable: true` `$ref` (`anyOf: [$ref, { type:
168
+ * "null" }]`).
169
+ */
170
+ const isNullType = (schema: SchemaLike): boolean => {
171
+ const types = declaredTypeList(schema.type);
172
+ return types.length > 0 && types.every((type) => type === "null");
160
173
  };
161
174
 
162
175
  /**
163
176
  * A short, human-readable type label for a schema row. `$ref`s label by name
164
177
  * (`Pet`, `Pet[]`) without resolving — which also means circular refs through
165
- * array items can't recurse forever.
178
+ * array items can't recurse forever. A `null` member stays out of a union's
179
+ * label: rows mark it with {@link isNullable}, as they do `type: [T, "null"]`.
166
180
  */
167
181
  export const typeLabel = (schema: SchemaLike): string => {
168
182
  if (isString(schema.$ref)) {
@@ -170,24 +184,42 @@ export const typeLabel = (schema: SchemaLike): string => {
170
184
  }
171
185
  if (schema.oneOf || schema.anyOf) {
172
186
  const branches = schema.oneOf ?? schema.anyOf ?? [];
173
- const labels = branches.map((branch) => typeLabel(branch));
187
+ const members = branches.filter((branch) => !isNullType(branch));
188
+ if (members.length === 0 && branches.length > 0) {
189
+ return "null";
190
+ }
191
+ const labels = members.map((branch) => typeLabel(branch));
174
192
  return [...new Set(labels)].join(" | ") || "any";
175
193
  }
176
194
  if (schema.allOf) {
177
195
  return "object";
178
196
  }
179
197
  const types = nonNullTypes(schema.type);
198
+ const arrayLabel = (): string => `${typeLabel(schema.items ?? {})}[]`;
199
+ if (types.length > 1) {
200
+ // A 3.1 type array names every type the value may take, not just the first.
201
+ return types
202
+ .map((type) => (type === "array" ? arrayLabel() : type))
203
+ .join(" | ");
204
+ }
180
205
  if (types.includes("array")) {
181
- return `${typeLabel(schema.items ?? {})}[]`;
206
+ return arrayLabel();
207
+ }
208
+ if (types.length === 0 && isNullType(schema)) {
209
+ return "null";
182
210
  }
183
211
  const base = types[0] ?? (schema.properties ? "object" : "any");
184
212
  return schema.format ? `${base}<${schema.format}>` : base;
185
213
  };
186
214
 
187
- /** Whether this schema is nullable (3.0 `nullable` or a 3.1 `"null"` in `type`). */
215
+ /**
216
+ * Whether this schema is nullable: 3.0 `nullable`, a 3.1 `"null"` in `type`,
217
+ * or a `{ type: "null" }` member of a `oneOf`/`anyOf` union.
218
+ */
188
219
  export const isNullable = (schema: SchemaLike): boolean =>
189
220
  schema.nullable === true ||
190
- (Array.isArray(schema.type) && schema.type.includes("null"));
221
+ (Array.isArray(schema.type) && schema.type.includes("null")) ||
222
+ [...(schema.oneOf ?? []), ...(schema.anyOf ?? [])].some(isNullType);
191
223
 
192
224
  /** Human-readable validation constraints for a schema, in display order. */
193
225
  export const constraints = (schema: SchemaLike): string[] => {
@@ -263,23 +295,26 @@ export const objectProperties = (
263
295
  * Build a representative example value for a schema via openapi-sampler
264
296
  * (Redoc's generator): declared `example`/`const`/`default`/`enum` values
265
297
  * win, formats produce realistic placeholders (`email`, `uuid`, `date-time`),
266
- * `readOnly` fields are skipped (these samples illustrate *requests*, and a
267
- * server-generated field has no place in one), and circular `$ref` chains —
268
- * which keeping refs intact allows — terminate safely.
298
+ * and circular `$ref` chains — which keeping refs intact allows — terminate
299
+ * safely. A request sample skips `readOnly` fields (a server-generated field
300
+ * has no place in one); a response sample skips `writeOnly` fields (a
301
+ * password the client sends never comes back) and keeps the `readOnly` ones.
269
302
  */
270
303
  export const exampleValue = (
271
304
  schema: SchemaLike | undefined,
272
- schemas: Record<string, SchemaLike>
305
+ schemas: Record<string, SchemaLike>,
306
+ direction: "request" | "response" = "request"
273
307
  ): SpecValue => {
274
308
  if (!schema) {
275
309
  return null;
276
310
  }
311
+ const response = direction === "response";
277
312
  try {
278
313
  // SAFETY: SchemaLike structurally covers the JSONSchema7 fields the
279
314
  // sampler reads, and the sampler only ever assembles JSON values.
280
315
  return sample(
281
316
  schema as Parameters<typeof sample>[0],
282
- { quiet: true, skipReadOnly: true },
317
+ { quiet: true, skipReadOnly: !response, skipWriteOnly: response },
283
318
  { components: { schemas } }
284
319
  ) as SpecValue;
285
320
  } catch {
@@ -289,5 +324,48 @@ export const exampleValue = (
289
324
  }
290
325
  };
291
326
 
327
+ /** Where an author can declare an example: a parameter or a media type. */
328
+ export interface ExampleCarrier {
329
+ example?: SpecValue;
330
+ examples?: SpecValue;
331
+ }
332
+
333
+ const isRecord = (value: SpecValue): value is Record<string, SpecValue> =>
334
+ typeof value === "object" && value !== null && !Array.isArray(value);
335
+
336
+ /**
337
+ * The example a parameter or media type declares: its `example`, else the
338
+ * first inline `value` in its `examples` map. Scalar's upgrade moves every
339
+ * OpenAPI 3.0 and Swagger 2.0 `example` into `examples.default.value`, and
340
+ * 3.1 authors write that map directly, so reading `example` alone would drop
341
+ * them all. `undefined` when nothing is declared.
342
+ */
343
+ export const declaredExample = (carrier: ExampleCarrier): SpecValue => {
344
+ if (carrier.example !== undefined) {
345
+ return carrier.example;
346
+ }
347
+ const { examples } = carrier;
348
+ if (!isRecord(examples)) {
349
+ return undefined;
350
+ }
351
+ for (const entry of Object.values(examples)) {
352
+ if (isRecord(entry) && entry.value !== undefined) {
353
+ return entry.value;
354
+ }
355
+ }
356
+ return undefined;
357
+ };
358
+
359
+ /**
360
+ * A response media type's example for the Response panel: the declared one,
361
+ * else a sample built for the response direction (`readOnly` kept,
362
+ * `writeOnly` skipped).
363
+ */
364
+ export const responseExample = (
365
+ media: ExampleCarrier & { schema?: SchemaLike },
366
+ schemas: Record<string, SchemaLike>
367
+ ): SpecValue =>
368
+ declaredExample(media) ?? exampleValue(media.schema, schemas, "response");
369
+
292
370
  /** Pretty-print a JSON value for an example/code block. */
293
371
  export const toJson = <T>(value: T): string => JSON.stringify(value, null, 2);
@@ -199,6 +199,14 @@ export const initComposer = (root: HTMLElement): void => {
199
199
 
200
200
  const client = createWsClient({ onFrame: renderFrame, onState: renderState });
201
201
 
202
+ // A ClientRouter navigation swaps this panel out of the document without
203
+ // unloading the page, so a live socket would stay open behind the next page
204
+ // — and coming back and connecting again would open a second one. Every
205
+ // swap replaces the panel, so the first one closes the socket for good.
206
+ document.addEventListener("astro:before-swap", () => client.disconnect(), {
207
+ once: true,
208
+ });
209
+
202
210
  connectButton?.addEventListener("click", () => {
203
211
  if (validatePayload().length > 0) {
204
212
  return;
@@ -2,6 +2,7 @@ import type {
2
2
  AsyncApiAction,
3
3
  AsyncApiServerObject,
4
4
  } from "../../openapi/asyncapi.ts";
5
+ import { withServerDefaults } from "../../openapi/model.ts";
5
6
  import type { AsyncApiMessageLike, NamedMessage } from "./async.ts";
6
7
  import { payloadSchema } from "./async.ts";
7
8
  import { exampleValue, toJson } from "./helpers.ts";
@@ -78,9 +79,18 @@ const messagePayload = (
78
79
  /**
79
80
  * A server's base URL for the picker. Falls back to the bare host when the
80
81
  * spec omits `protocol` — a label is a label, and the snippet builders read
81
- * the server object itself rather than this string.
82
+ * the server object itself rather than this string. Server variables in the
83
+ * host and pathname (`{env}.events.example.com`) resolve to their declared
84
+ * defaults, in the label and in the object the samples and connect URL read.
82
85
  */
83
- const serverOption = (server: AsyncApiServerObject): MessageServerOption => {
86
+ const serverOption = (spec: AsyncApiServerObject): MessageServerOption => {
87
+ const server = { ...spec };
88
+ if (spec.host !== undefined) {
89
+ server.host = withServerDefaults(spec.host, spec.variables);
90
+ }
91
+ if (spec.pathname !== undefined) {
92
+ server.pathname = withServerDefaults(spec.pathname, spec.variables);
93
+ }
84
94
  const host = `${server.host ?? ""}${server.pathname ?? ""}`;
85
95
  return {
86
96
  label: server.protocol ? `${server.protocol}://${host}` : host,
@@ -40,7 +40,10 @@ export interface MessagePayload {
40
40
  export interface MessageServerOption {
41
41
  /** `protocol://host+pathname` — what the reader picks between. */
42
42
  label: string;
43
- /** The spec server, verbatim; the snippet builders read host/pathname/protocol. */
43
+ /**
44
+ * The spec server with its host/pathname variables at their defaults; the
45
+ * snippet builders read host/pathname/protocol.
46
+ */
44
47
  server: AsyncApiServerObject;
45
48
  }
46
49