blume 2.0.0 → 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 (454) hide show
  1. package/AGENTS.md +1 -1
  2. package/CHANGELOG.md +110 -0
  3. package/README.md +2 -2
  4. package/dist/cli/{chunk-f2972sbt.js → chunk-00gs3wqs.js} +1 -1
  5. package/dist/cli/{chunk-fa25z98p.js → chunk-273ygyr4.js} +4 -3
  6. package/dist/cli/chunk-273ygyr4.js.map +11 -0
  7. package/dist/cli/{chunk-pat2zzwc.js → chunk-3yce002v.js} +21 -8
  8. package/dist/cli/{chunk-pat2zzwc.js.map → chunk-3yce002v.js.map} +4 -4
  9. package/dist/cli/{chunk-d1tadaw7.js → chunk-4e9b9ra6.js} +17 -7
  10. package/dist/cli/chunk-4e9b9ra6.js.map +10 -0
  11. package/dist/cli/{chunk-zg2gtj10.js → chunk-5r8g91qn.js} +352 -676
  12. package/dist/cli/chunk-5r8g91qn.js.map +34 -0
  13. package/dist/cli/{chunk-11j0384y.js → chunk-6dtt0zfn.js} +14 -14
  14. package/dist/cli/{chunk-11j0384y.js.map → chunk-6dtt0zfn.js.map} +3 -3
  15. package/dist/cli/{chunk-bw22s759.js → chunk-6k4ftwze.js} +38 -19
  16. package/dist/cli/{chunk-bw22s759.js.map → chunk-6k4ftwze.js.map} +4 -4
  17. package/dist/cli/{chunk-sqn5t4q0.js → chunk-7mbqtmgb.js} +16 -7
  18. package/dist/cli/chunk-7mbqtmgb.js.map +10 -0
  19. package/dist/cli/{chunk-79njf86q.js → chunk-7vtckvaw.js} +21 -19
  20. package/dist/cli/chunk-7vtckvaw.js.map +11 -0
  21. package/dist/cli/{chunk-fh5hj5jt.js → chunk-8g8ytmgx.js} +17 -18
  22. package/dist/cli/{chunk-fh5hj5jt.js.map → chunk-8g8ytmgx.js.map} +2 -2
  23. package/dist/cli/{chunk-1w8dp3qb.js → chunk-91ws1n6j.js} +18 -15
  24. package/dist/cli/chunk-91ws1n6j.js.map +10 -0
  25. package/dist/cli/{chunk-7ez8ny0t.js → chunk-bbnwccaz.js} +2 -2
  26. package/dist/cli/{chunk-5a2z0198.js → chunk-bfwp9vp6.js} +18 -10
  27. package/dist/cli/chunk-bfwp9vp6.js.map +10 -0
  28. package/dist/cli/{chunk-ernrthtr.js → chunk-d1v5rhy0.js} +14 -15
  29. package/dist/cli/{chunk-ernrthtr.js.map → chunk-d1v5rhy0.js.map} +2 -2
  30. package/dist/cli/{chunk-a9kptbw5.js → chunk-ddndchfr.js} +21 -6
  31. package/dist/cli/chunk-ddndchfr.js.map +14 -0
  32. package/dist/cli/{chunk-zxccj738.js → chunk-esh98wmb.js} +1 -1
  33. package/dist/cli/{chunk-88cpgt6h.js → chunk-fsmrqk8a.js} +1 -1
  34. package/dist/cli/{chunk-b5aj94ah.js → chunk-g698a744.js} +5 -5
  35. package/dist/cli/{chunk-jts8mvcz.js → chunk-gs7r695n.js} +9 -3
  36. package/dist/cli/{chunk-jts8mvcz.js.map → chunk-gs7r695n.js.map} +3 -3
  37. package/dist/cli/{chunk-41za066z.js → chunk-h2ez8dzb.js} +4 -4
  38. package/dist/cli/{chunk-by2290sx.js → chunk-h7k3nq3v.js} +2 -2
  39. package/dist/cli/{chunk-j8mw0za6.js → chunk-hqp2ajnh.js} +252 -123
  40. package/dist/cli/chunk-hqp2ajnh.js.map +35 -0
  41. package/dist/cli/{chunk-mnqj32sj.js → chunk-hr8ne106.js} +119 -50
  42. package/dist/cli/chunk-hr8ne106.js.map +13 -0
  43. package/dist/cli/{chunk-mwt1k8n7.js → chunk-j85scx15.js} +74 -30
  44. package/dist/cli/chunk-j85scx15.js.map +10 -0
  45. package/dist/cli/{chunk-z01ze5c1.js → chunk-k7pj68a8.js} +63 -22
  46. package/dist/cli/chunk-k7pj68a8.js.map +11 -0
  47. package/dist/cli/{chunk-2q1dwty4.js → chunk-mqc662a6.js} +8 -8
  48. package/dist/cli/{chunk-2q1dwty4.js.map → chunk-mqc662a6.js.map} +4 -4
  49. package/dist/cli/{chunk-y3e45rc8.js → chunk-n1yg3tj3.js} +4 -4
  50. package/dist/cli/{chunk-bctazmbk.js → chunk-nfcyttvj.js} +17 -6
  51. package/dist/cli/chunk-nfcyttvj.js.map +10 -0
  52. package/dist/cli/{chunk-6k8vp3ta.js → chunk-pbg5a4s3.js} +60 -28
  53. package/dist/cli/chunk-pbg5a4s3.js.map +19 -0
  54. package/dist/cli/{chunk-d80hr03s.js → chunk-ppzjqwx2.js} +24 -17
  55. package/dist/cli/{chunk-d80hr03s.js.map → chunk-ppzjqwx2.js.map} +4 -4
  56. package/dist/cli/{chunk-beat36xx.js → chunk-qkb5a8sa.js} +25 -10
  57. package/dist/cli/chunk-qkb5a8sa.js.map +10 -0
  58. package/dist/cli/{chunk-bnbmcwfb.js → chunk-sqw4ekg1.js} +5 -5
  59. package/dist/cli/{chunk-bnbmcwfb.js.map → chunk-sqw4ekg1.js.map} +3 -3
  60. package/dist/cli/{chunk-xaz13gwg.js → chunk-v6ya5kcb.js} +3268 -1150
  61. package/dist/cli/chunk-v6ya5kcb.js.map +189 -0
  62. package/dist/cli/{chunk-tzne8qfq.js → chunk-w4bxdvsa.js} +18 -15
  63. package/dist/cli/chunk-w4bxdvsa.js.map +10 -0
  64. package/dist/cli/{chunk-f7t03s3g.js → chunk-wdrt2k2v.js} +2 -2
  65. package/dist/cli/{chunk-z1f5arsg.js → chunk-xh43dwgw.js} +170 -55
  66. package/dist/cli/chunk-xh43dwgw.js.map +36 -0
  67. package/dist/cli/{chunk-pnnvybbk.js → chunk-zp79m0ts.js} +5 -5
  68. package/dist/cli/{chunk-pnnvybbk.js.map → chunk-zp79m0ts.js.map} +2 -2
  69. package/dist/cli/index.js +160 -35
  70. package/dist/cli/index.js.map +4 -4
  71. package/dist/types/ai/agent-readability.d.ts +1 -1
  72. package/dist/types/ai/agent-surface.d.ts +32 -0
  73. package/dist/types/ai/api/paths.d.ts +1 -1
  74. package/dist/types/ai/api-catalog.d.ts +7 -1
  75. package/dist/types/ai/ask-context.d.ts +14 -7
  76. package/dist/types/ai/ask.d.ts +43 -43
  77. package/dist/types/ai/component-markdown.d.ts +4 -4
  78. package/dist/types/ai/index.d.ts +3 -3
  79. package/dist/types/ai/openapi-components.d.ts +1 -1
  80. package/dist/types/ai/relative-links.d.ts +9 -4
  81. package/dist/types/ai/serializers.d.ts +1 -1
  82. package/dist/types/ai/static-expression.d.ts +28 -0
  83. package/dist/types/ai/visibility.d.ts +1 -1
  84. package/dist/types/analytics/databuddy.d.ts +43 -0
  85. package/dist/types/analytics/index.d.ts +2 -0
  86. package/dist/types/analytics/schema.d.ts +14 -0
  87. package/dist/types/cli/env.d.ts +5 -0
  88. package/dist/types/cli/init/scaffold.d.ts +19 -3
  89. package/dist/types/cli/init/starter-spec.d.ts +11 -0
  90. package/dist/types/core/base-path.d.ts +9 -0
  91. package/dist/types/core/config-input.d.ts +21 -21
  92. package/dist/types/core/config.d.ts +5 -5
  93. package/dist/types/core/data.d.ts +3 -3
  94. package/dist/types/core/graph.d.ts +2 -0
  95. package/dist/types/core/i18n-ui.d.ts +37 -8
  96. package/dist/types/core/i18n.d.ts +7 -1
  97. package/dist/types/core/links.d.ts +3 -1
  98. package/dist/types/core/load-module.d.ts +10 -0
  99. package/dist/types/core/locale-links.d.ts +12 -2
  100. package/dist/types/core/meta.d.ts +5 -1
  101. package/dist/types/core/nav-diagnostics.d.ts +10 -0
  102. package/dist/types/core/navigation.d.ts +38 -0
  103. package/dist/types/core/ordering-prefix.d.ts +4 -0
  104. package/dist/types/core/safe-links.d.ts +3 -1
  105. package/dist/types/core/schema.d.ts +57 -18
  106. package/dist/types/core/sources/github-releases.d.ts +5 -0
  107. package/dist/types/core/sources/lower.d.ts +14 -2
  108. package/dist/types/core/sources/normalize.d.ts +10 -1
  109. package/dist/types/core/sources/remote.d.ts +11 -1
  110. package/dist/types/core/sources/resolve.d.ts +12 -0
  111. package/dist/types/core/sources/types.d.ts +31 -0
  112. package/dist/types/core/types.d.ts +9 -0
  113. package/dist/types/core/unrecognized-keys.d.ts +1 -1
  114. package/dist/types/deploy/adapters/node.d.ts +5 -2
  115. package/dist/types/deploy/adapters/types.d.ts +7 -0
  116. package/dist/types/deploy/cloudflare-negotiation.d.ts +3 -2
  117. package/dist/types/deploy/headers.d.ts +31 -7
  118. package/dist/types/deploy/node-headers.d.ts +43 -8
  119. package/dist/types/deploy/platforms/netlify.d.ts +27 -2
  120. package/dist/types/deploy/platforms/node.d.ts +6 -5
  121. package/dist/types/deploy/platforms/types.d.ts +7 -0
  122. package/dist/types/deploy/platforms/vercel.d.ts +3 -2
  123. package/dist/types/deploy/redirects.d.ts +7 -2
  124. package/dist/types/deploy/vercel-negotiation.d.ts +3 -2
  125. package/dist/types/openapi/model.d.ts +20 -6
  126. package/dist/types/search/documents.d.ts +1 -1
  127. package/dist/types/search/orama-index.d.ts +1 -1
  128. package/dist/types/search/sync/algolia.d.ts +3 -1
  129. package/docs/01-quickstart.mdx +3 -2
  130. package/docs/02-deployment.mdx +21 -12
  131. package/docs/03-upgrading.mdx +22 -9
  132. package/docs/04-migrating.mdx +4 -4
  133. package/docs/08-faq.mdx +12 -5
  134. package/docs/advanced/changelog.mdx +1 -1
  135. package/docs/advanced/custom-pages.mdx +5 -3
  136. package/docs/advanced/skills.mdx +1 -1
  137. package/docs/cli/audit.mdx +5 -5
  138. package/docs/cli/doctor.mdx +4 -4
  139. package/docs/cli/evals.mdx +9 -9
  140. package/docs/cli/index.mdx +6 -3
  141. package/docs/cli/translate.mdx +8 -8
  142. package/docs/configuration/analytics.mdx +23 -2
  143. package/docs/configuration/{ask-ai.mdx → assistant.mdx} +20 -20
  144. package/docs/configuration/customization.mdx +5 -4
  145. package/docs/configuration/index.mdx +7 -5
  146. package/docs/configuration/meta.ts +1 -1
  147. package/docs/configuration/search.mdx +2 -2
  148. package/docs/content/components.mdx +1 -1
  149. package/docs/content/frontmatter.mdx +5 -1
  150. package/docs/content/i18n.mdx +3 -3
  151. package/docs/content/index.mdx +4 -2
  152. package/docs/content/islands.mdx +1 -1
  153. package/docs/content/meta.mdx +5 -3
  154. package/docs/content/navigation.mdx +29 -4
  155. package/docs/content/sources.mdx +18 -12
  156. package/docs/content/versioning.mdx +1 -0
  157. package/docs/discoverability/agent-discovery.mdx +22 -8
  158. package/docs/discoverability/index.mdx +3 -3
  159. package/docs/discoverability/llms-txt.mdx +2 -5
  160. package/docs/discoverability/markdown.mdx +5 -3
  161. package/docs/discoverability/mcp.mdx +4 -0
  162. package/docs/discoverability/metadata.mdx +3 -2
  163. package/docs/discoverability/open-graph.mdx +6 -4
  164. package/docs/discoverability/rss.mdx +3 -1
  165. package/docs/index.mdx +2 -2
  166. package/docs/references/asyncapi.mdx +1 -1
  167. package/docs/references/graphql.mdx +2 -2
  168. package/docs/references/openapi.mdx +3 -3
  169. package/package.json +1 -1
  170. package/skills/blume/SKILL.md +5 -5
  171. package/skills/blume-migrate/SKILL.md +6 -6
  172. package/skills/blume-migrate/references/docusaurus.md +6 -6
  173. package/skills/blume-migrate/references/mintlify.md +3 -3
  174. package/skills/blume-migrate/references/monorepo.md +1 -1
  175. package/skills/blume-migrate/references/nextra.md +1 -1
  176. package/skills/blume-migrate/references/starlight.md +5 -5
  177. package/skills/blume-migrate/scripts/mintlify-codemod.mjs +2 -2
  178. package/skills/blume-update-docs/references/audit-checklist.md +1 -1
  179. package/src/ai/agent-readability.ts +4 -4
  180. package/src/ai/agent-surface.ts +56 -0
  181. package/src/ai/api/paths.ts +1 -1
  182. package/src/ai/api/spec.ts +15 -2
  183. package/src/ai/api-catalog.ts +7 -1
  184. package/src/ai/ask-context.ts +21 -9
  185. package/src/ai/ask-data.ts +28 -14
  186. package/src/ai/ask.ts +84 -71
  187. package/src/ai/component-markdown.ts +43 -37
  188. package/src/ai/cors.ts +3 -3
  189. package/src/ai/index.ts +16 -16
  190. package/src/ai/link-headers.ts +8 -3
  191. package/src/ai/llms.ts +4 -3
  192. package/src/ai/markdown.ts +23 -11
  193. package/src/ai/mcp/server.ts +78 -7
  194. package/src/ai/openapi-components.ts +1 -1
  195. package/src/ai/relative-links.ts +78 -10
  196. package/src/ai/serializers.ts +1 -1
  197. package/src/ai/static-expression.ts +416 -0
  198. package/src/ai/visibility.ts +46 -15
  199. package/src/analytics/databuddy.ts +67 -0
  200. package/src/analytics/head.ts +4 -0
  201. package/src/analytics/index.ts +2 -0
  202. package/src/analytics/posthog.ts +21 -3
  203. package/src/analytics/schema.ts +2 -0
  204. package/src/astro/generate.ts +90 -45
  205. package/src/astro/module-types.ts +1 -1
  206. package/src/astro/runtime-deps.ts +6 -6
  207. package/src/astro/templates.ts +87 -49
  208. package/src/audit/catalog.ts +2 -2
  209. package/src/audit/checks/assets.ts +25 -5
  210. package/src/audit/checks/content.ts +20 -2
  211. package/src/audit/checks/i18n.ts +46 -8
  212. package/src/audit/checks/indexability.ts +39 -19
  213. package/src/audit/checks/links.ts +14 -0
  214. package/src/audit/checks/llms.ts +6 -3
  215. package/src/audit/checks/network.ts +3 -1
  216. package/src/audit/checks/og-image.ts +10 -0
  217. package/src/audit/checks/robots.ts +6 -1
  218. package/src/audit/checks/sitemap.ts +56 -31
  219. package/src/audit/checks/social.ts +21 -2
  220. package/src/audit/crawl.ts +88 -15
  221. package/src/audit/report.ts +54 -17
  222. package/src/audit/run.ts +1 -0
  223. package/src/audit/types.ts +18 -0
  224. package/src/audit/url.ts +37 -6
  225. package/src/blume-modules.d.ts +2 -2
  226. package/src/cli/build-failure.ts +50 -0
  227. package/src/cli/commands/audit.ts +8 -1
  228. package/src/cli/commands/build.ts +19 -5
  229. package/src/cli/commands/check.ts +2 -0
  230. package/src/cli/commands/doctor.ts +7 -5
  231. package/src/cli/commands/eval.ts +4 -10
  232. package/src/cli/commands/init.ts +15 -5
  233. package/src/cli/commands/migrate.ts +2 -2
  234. package/src/cli/commands/preview.ts +15 -0
  235. package/src/cli/commands/sync.ts +2 -0
  236. package/src/cli/commands/translate.ts +12 -9
  237. package/src/cli/commands/upgrade.ts +13 -2
  238. package/src/cli/eject-scripts.ts +32 -7
  239. package/src/cli/env.ts +12 -1
  240. package/src/cli/init/scaffold.ts +117 -11
  241. package/src/cli/init/starter-spec.ts +235 -0
  242. package/src/cli/required-secrets.ts +3 -3
  243. package/src/components/content/AccordionItem.astro +26 -22
  244. package/src/components/content/Badge.astro +2 -9
  245. package/src/components/content/Card.astro +2 -2
  246. package/src/components/content/Component.astro +9 -3
  247. package/src/components/content/Expandable.astro +5 -1
  248. package/src/components/content/Frame.astro +2 -7
  249. package/src/components/content/GithubInfo.astro +12 -2
  250. package/src/components/content/Prompt.astro +2 -7
  251. package/src/components/content/Tabs.astro +54 -5
  252. package/src/components/content/Tile.astro +1 -1
  253. package/src/components/content/Tooltip.astro +69 -7
  254. package/src/components/content/Tree.astro +7 -2
  255. package/src/components/content/TypeTable.astro +10 -5
  256. package/src/components/content/Update.astro +8 -2
  257. package/src/components/content/auto-type-table.ts +4 -1
  258. package/src/components/content/badge-color.ts +17 -0
  259. package/src/components/content/base-href.ts +18 -3
  260. package/src/components/content/inline-markdown.ts +27 -7
  261. package/src/components/copy-feedback.ts +36 -9
  262. package/src/components/islands/{AskAI.astro → Assistant.astro} +10 -10
  263. package/src/components/islands/{ask-ai.tsx → assistant.tsx} +39 -25
  264. package/src/components/islands/hooks.ts +21 -14
  265. package/src/components/islands/webmcp.ts +12 -8
  266. package/src/components/layout/Banner.astro +23 -4
  267. package/src/components/layout/Header.astro +32 -19
  268. package/src/components/layout/Logo.astro +5 -0
  269. package/src/components/layout/NavSelector.astro +6 -1
  270. package/src/components/layout/NavTabMenu.astro +133 -0
  271. package/src/components/layout/NavTree.astro +15 -6
  272. package/src/components/layout/NavTreeCache.astro +5 -2
  273. package/src/components/layout/NavTreeScript.astro +45 -5
  274. package/src/components/layout/PageLayout.astro +28 -14
  275. package/src/components/layout/Pagination.astro +7 -7
  276. package/src/components/layout/ReferenceLayout.astro +5 -2
  277. package/src/components/layout/RootLayout.astro +27 -14
  278. package/src/components/layout/Search.astro +18 -14
  279. package/src/components/layout/analytics-client.ts +3 -1
  280. package/src/components/layout/drawer-inert.ts +1 -1
  281. package/src/components/openapi/GraphqlType.astro +11 -3
  282. package/src/components/openapi/MessageComposer.astro +1 -1
  283. package/src/components/openapi/Operation.astro +21 -5
  284. package/src/components/openapi/PanelTabs.astro +4 -1
  285. package/src/components/openapi/Playground.astro +8 -2
  286. package/src/components/openapi/RequestPanel.astro +9 -5
  287. package/src/components/openapi/SchemaProperty.astro +11 -33
  288. package/src/components/openapi/SchemaTable.astro +27 -52
  289. package/src/components/openapi/description.ts +2 -2
  290. package/src/components/openapi/helpers.ts +90 -12
  291. package/src/components/openapi/message-composer.ts +8 -0
  292. package/src/components/openapi/message-model.ts +12 -2
  293. package/src/components/openapi/message.ts +4 -1
  294. package/src/components/openapi/operation-model.ts +40 -8
  295. package/src/components/openapi/panel.ts +29 -5
  296. package/src/components/openapi/playground-client.ts +37 -7
  297. package/src/components/openapi/playground-schema.ts +25 -6
  298. package/src/components/openapi/request.ts +2 -0
  299. package/src/components/openapi/schema-tree.ts +203 -0
  300. package/src/components/openapi/snippets.ts +30 -15
  301. package/src/components/openapi/validate-json.ts +1 -1
  302. package/src/core/base-path.ts +15 -0
  303. package/src/core/code-fences.ts +1 -1
  304. package/src/core/config-input.ts +23 -23
  305. package/src/core/config.ts +21 -7
  306. package/src/core/data.ts +3 -3
  307. package/src/core/diagnostics.ts +217 -30
  308. package/src/core/graph.ts +120 -11
  309. package/src/core/i18n-ui.ts +74 -11
  310. package/src/core/i18n.ts +13 -2
  311. package/src/core/links.ts +7 -4
  312. package/src/core/load-module.ts +20 -0
  313. package/src/core/locale-links.ts +17 -17
  314. package/src/core/manifest.ts +3 -2
  315. package/src/core/meta.ts +69 -6
  316. package/src/core/nav-diagnostics.ts +56 -1
  317. package/src/core/navigation.ts +245 -76
  318. package/src/core/ordering-prefix.ts +27 -0
  319. package/src/core/project-graph.ts +23 -1
  320. package/src/core/request-body.ts +1 -1
  321. package/src/core/safe-href.ts +53 -1
  322. package/src/core/safe-links.ts +11 -2
  323. package/src/core/schema.ts +95 -34
  324. package/src/core/server-features.ts +2 -2
  325. package/src/core/sources/assets.ts +83 -41
  326. package/src/core/sources/contentful-rich-text.ts +3 -2
  327. package/src/core/sources/contentful.ts +25 -12
  328. package/src/core/sources/filesystem.ts +5 -1
  329. package/src/core/sources/github-releases.ts +106 -5
  330. package/src/core/sources/lexical.ts +9 -4
  331. package/src/core/sources/lower.ts +79 -26
  332. package/src/core/sources/mdx-remote.ts +1 -0
  333. package/src/core/sources/normalize.ts +132 -30
  334. package/src/core/sources/notion.ts +26 -10
  335. package/src/core/sources/obsidian.ts +17 -3
  336. package/src/core/sources/payload.ts +1 -0
  337. package/src/core/sources/portable-text.ts +63 -44
  338. package/src/core/sources/remote.ts +18 -2
  339. package/src/core/sources/resolve.ts +52 -33
  340. package/src/core/sources/sanity.ts +1 -0
  341. package/src/core/sources/strapi-blocks.ts +9 -4
  342. package/src/core/sources/strapi.ts +1 -0
  343. package/src/core/sources/types.ts +31 -0
  344. package/src/core/types.ts +9 -0
  345. package/src/core/ui-packs/ar.ts +17 -5
  346. package/src/core/ui-packs/bg.ts +17 -5
  347. package/src/core/ui-packs/bn.ts +17 -5
  348. package/src/core/ui-packs/ca.ts +17 -5
  349. package/src/core/ui-packs/cs.ts +17 -5
  350. package/src/core/ui-packs/da.ts +17 -5
  351. package/src/core/ui-packs/de.ts +17 -5
  352. package/src/core/ui-packs/el.ts +17 -5
  353. package/src/core/ui-packs/es.ts +17 -5
  354. package/src/core/ui-packs/fa.ts +17 -5
  355. package/src/core/ui-packs/fi.ts +17 -5
  356. package/src/core/ui-packs/fr.ts +17 -5
  357. package/src/core/ui-packs/he.ts +17 -5
  358. package/src/core/ui-packs/hi.ts +17 -5
  359. package/src/core/ui-packs/hr.ts +17 -5
  360. package/src/core/ui-packs/hu.ts +17 -5
  361. package/src/core/ui-packs/id.ts +17 -5
  362. package/src/core/ui-packs/it.ts +17 -5
  363. package/src/core/ui-packs/ja.ts +17 -5
  364. package/src/core/ui-packs/ko.ts +17 -5
  365. package/src/core/ui-packs/nl.ts +17 -5
  366. package/src/core/ui-packs/no.ts +17 -5
  367. package/src/core/ui-packs/pl.ts +17 -5
  368. package/src/core/ui-packs/pt-br.ts +17 -5
  369. package/src/core/ui-packs/pt.ts +17 -5
  370. package/src/core/ui-packs/ro.ts +17 -5
  371. package/src/core/ui-packs/ru.ts +17 -5
  372. package/src/core/ui-packs/sk.ts +17 -5
  373. package/src/core/ui-packs/sr.ts +17 -5
  374. package/src/core/ui-packs/sv.ts +17 -5
  375. package/src/core/ui-packs/th.ts +17 -5
  376. package/src/core/ui-packs/tr.ts +17 -5
  377. package/src/core/ui-packs/uk.ts +17 -5
  378. package/src/core/ui-packs/vi.ts +17 -5
  379. package/src/core/ui-packs/zh-tw.ts +17 -5
  380. package/src/core/ui-packs/zh.ts +17 -5
  381. package/src/core/unrecognized-keys.ts +1 -1
  382. package/src/core/version-cut.ts +17 -2
  383. package/src/core/versions.ts +4 -1
  384. package/src/deploy/adapters/node.ts +5 -2
  385. package/src/deploy/adapters/registry.ts +2 -1
  386. package/src/deploy/adapters/types.ts +13 -1
  387. package/src/deploy/artifacts.ts +25 -5
  388. package/src/deploy/cloudflare-negotiation.ts +23 -4
  389. package/src/deploy/headers.ts +67 -51
  390. package/src/deploy/node-headers.ts +148 -27
  391. package/src/deploy/platforms/cloudflare.ts +1 -0
  392. package/src/deploy/platforms/netlify.ts +82 -5
  393. package/src/deploy/platforms/node.ts +7 -5
  394. package/src/deploy/platforms/static.ts +1 -0
  395. package/src/deploy/platforms/types.ts +7 -0
  396. package/src/deploy/platforms/vercel.ts +9 -3
  397. package/src/deploy/redirects.ts +14 -3
  398. package/src/deploy/vercel-negotiation.ts +35 -3
  399. package/src/eval/agents.ts +10 -2
  400. package/src/eval/run.ts +25 -0
  401. package/src/markdown/base-links.ts +55 -8
  402. package/src/markdown/index.ts +6 -3
  403. package/src/markdown/relative-links.ts +3 -23
  404. package/src/markdown/route-snapshot.ts +37 -0
  405. package/src/og/card.ts +20 -4
  406. package/src/og/derive.ts +145 -4
  407. package/src/openapi/graphql-build.ts +28 -2
  408. package/src/openapi/model.ts +77 -13
  409. package/src/openapi/render-mdx.ts +10 -1
  410. package/src/registry/eject.ts +135 -37
  411. package/src/search/documents.ts +24 -3
  412. package/src/search/orama-index.ts +1 -1
  413. package/src/search/sync/algolia.ts +36 -2
  414. package/src/sources/registry.ts +5 -0
  415. package/src/theme/entry.ts +11 -4
  416. package/src/translate/agents.ts +6 -1
  417. package/src/translate/ledger.ts +26 -3
  418. package/src/translate/meta.ts +11 -3
  419. package/src/translate/report.ts +1 -1
  420. package/src/translate/run.ts +11 -5
  421. package/src/translate/validate.ts +10 -1
  422. package/src/translate/work-list.ts +36 -3
  423. package/src/upgrade/upgrade.ts +37 -5
  424. package/dist/cli/chunk-1w8dp3qb.js.map +0 -10
  425. package/dist/cli/chunk-5a2z0198.js.map +0 -10
  426. package/dist/cli/chunk-6k8vp3ta.js.map +0 -19
  427. package/dist/cli/chunk-79njf86q.js.map +0 -11
  428. package/dist/cli/chunk-a9kptbw5.js.map +0 -14
  429. package/dist/cli/chunk-abh8yjkn.js +0 -31
  430. package/dist/cli/chunk-abh8yjkn.js.map +0 -10
  431. package/dist/cli/chunk-bctazmbk.js.map +0 -10
  432. package/dist/cli/chunk-beat36xx.js.map +0 -10
  433. package/dist/cli/chunk-d1tadaw7.js.map +0 -10
  434. package/dist/cli/chunk-fa25z98p.js.map +0 -11
  435. package/dist/cli/chunk-j8mw0za6.js.map +0 -35
  436. package/dist/cli/chunk-mnqj32sj.js.map +0 -12
  437. package/dist/cli/chunk-mwt1k8n7.js.map +0 -10
  438. package/dist/cli/chunk-nk3ts2xk.js +0 -51
  439. package/dist/cli/chunk-nk3ts2xk.js.map +0 -10
  440. package/dist/cli/chunk-sqn5t4q0.js.map +0 -10
  441. package/dist/cli/chunk-tzne8qfq.js.map +0 -10
  442. package/dist/cli/chunk-xaz13gwg.js.map +0 -182
  443. package/dist/cli/chunk-z01ze5c1.js.map +0 -10
  444. package/dist/cli/chunk-z1f5arsg.js.map +0 -36
  445. package/dist/cli/chunk-zg2gtj10.js.map +0 -35
  446. /package/dist/cli/{chunk-f2972sbt.js.map → chunk-00gs3wqs.js.map} +0 -0
  447. /package/dist/cli/{chunk-7ez8ny0t.js.map → chunk-bbnwccaz.js.map} +0 -0
  448. /package/dist/cli/{chunk-zxccj738.js.map → chunk-esh98wmb.js.map} +0 -0
  449. /package/dist/cli/{chunk-88cpgt6h.js.map → chunk-fsmrqk8a.js.map} +0 -0
  450. /package/dist/cli/{chunk-b5aj94ah.js.map → chunk-g698a744.js.map} +0 -0
  451. /package/dist/cli/{chunk-41za066z.js.map → chunk-h2ez8dzb.js.map} +0 -0
  452. /package/dist/cli/{chunk-by2290sx.js.map → chunk-h7k3nq3v.js.map} +0 -0
  453. /package/dist/cli/{chunk-y3e45rc8.js.map → chunk-n1yg3tj3.js.map} +0 -0
  454. /package/dist/cli/{chunk-f7t03s3g.js.map → chunk-wdrt2k2v.js.map} +0 -0
@@ -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
 
@@ -1,4 +1,6 @@
1
+ import { withServerDefaults } from "../../openapi/model.ts";
1
2
  import {
3
+ declaredExample,
2
4
  exampleValue,
3
5
  objectProperties,
4
6
  resolveSchema,
@@ -30,9 +32,30 @@ import type { OperationSecurity, ResolvedScheme } from "./security.ts";
30
32
 
31
33
  interface MediaTypeLike {
32
34
  schema?: SchemaLike;
33
- example?: unknown;
35
+ example?: SpecValue;
36
+ examples?: SpecValue;
34
37
  }
35
38
 
39
+ /** A server entry: its URL template and the variables that fill it. */
40
+ export interface ServerLike {
41
+ url?: string;
42
+ variables?: SpecValue;
43
+ }
44
+
45
+ /**
46
+ * The servers an operation is sent to: its own `servers` when it declares
47
+ * any, else its path item's, else the document's — each level overrides the
48
+ * one above it, as the OpenAPI spec defines.
49
+ */
50
+ export const effectiveServers = (
51
+ operation: ServerLike[] | undefined,
52
+ pathItem: ServerLike[] | undefined,
53
+ document: ServerLike[] | undefined
54
+ ): ServerLike[] =>
55
+ [operation, pathItem, document].find(
56
+ (servers) => Array.isArray(servers) && servers.length > 0
57
+ ) ?? [];
58
+
36
59
  /** Primitive types the flat-body fields UI can edit directly. */
37
60
  const PRIMITIVE_TYPES = {
38
61
  boolean: true,
@@ -78,7 +101,9 @@ const modelParams = (
78
101
  required,
79
102
  type: scalarType(param.schema, schemas),
80
103
  value: required
81
- ? inputValue(param.example ?? exampleValue(param.schema, schemas))
104
+ ? inputValue(
105
+ declaredExample(param) ?? exampleValue(param.schema, schemas)
106
+ )
82
107
  : "",
83
108
  });
84
109
  }
@@ -127,7 +152,12 @@ const bodyFields = (
127
152
  description: propertySchema.description,
128
153
  enum: propertySchema.enum?.map(String),
129
154
  name,
130
- required: required.has(name),
155
+ // A `readOnly` property's `required` binds responses only: a request
156
+ // shouldn't send the server-generated field at all.
157
+ required:
158
+ required.has(name) &&
159
+ property.readOnly !== true &&
160
+ propertySchema.readOnly !== true,
131
161
  type,
132
162
  value: inputValue(defaults?.[name]),
133
163
  });
@@ -145,10 +175,8 @@ const modelBody = (
145
175
  return undefined;
146
176
  }
147
177
  const [contentType, mediaType] = media;
148
- // SAFETY: `example` comes from the parsed spec document (YAML/JSON), whose
149
- // values are exactly the JSON-shaped tree `SpecValue` models.
150
178
  const exampleData =
151
- (mediaType.example as SpecValue) ?? exampleValue(mediaType.schema, schemas);
179
+ declaredExample(mediaType) ?? exampleValue(mediaType.schema, schemas);
152
180
  return {
153
181
  contentType,
154
182
  example: toJson(exampleData) ?? "",
@@ -241,7 +269,8 @@ export const operationModel = (args: {
241
269
  /** Pre-merged/resolved (`mergeParameters` output). */
242
270
  parameters: ParameterLike[];
243
271
  requestBody?: { content?: Record<string, MediaTypeLike> };
244
- servers: { url?: string }[];
272
+ /** The operation's effective servers (`effectiveServers` output). */
273
+ servers: ServerLike[];
245
274
  schemas: Record<string, SchemaLike>;
246
275
  security: OperationSecurity;
247
276
  }): PlaygroundModel => ({
@@ -256,5 +285,8 @@ export const operationModel = (args: {
256
285
  method: args.method.toUpperCase(),
257
286
  params: modelParams(args.parameters, args.schemas),
258
287
  path: args.path,
259
- servers: args.servers.map((server) => server.url ?? ""),
288
+ // Variables resolve to their defaults: the samples and Send need a real URL.
289
+ servers: args.servers.map((server) =>
290
+ withServerDefaults(server.url ?? "", server.variables)
291
+ ),
260
292
  });
@@ -18,23 +18,47 @@ class BlumePanelTabs extends HTMLElement {
18
18
 
19
19
  const activate = (key: string): void => {
20
20
  for (const tab of tabs) {
21
- tab.setAttribute(
22
- "aria-selected",
23
- tab.dataset.panelTab === key ? "true" : "false"
24
- );
21
+ const selected = tab.dataset.panelTab === key;
22
+ tab.setAttribute("aria-selected", selected ? "true" : "false");
23
+ // Roving tabindex: Tab reaches the selected tab only, the arrow keys
24
+ // move between the rest.
25
+ tab.tabIndex = selected ? 0 : -1;
25
26
  }
26
27
  for (const panel of panels) {
27
28
  panel.classList.toggle("hidden", panel.dataset.panel !== key);
28
29
  }
29
30
  };
30
31
 
31
- for (const tab of tabs) {
32
+ for (const [index, tab] of tabs.entries()) {
32
33
  tab.addEventListener("click", () => {
33
34
  const key = tab.dataset.panelTab;
34
35
  if (key) {
35
36
  activate(key);
36
37
  }
37
38
  });
39
+ // The WAI-ARIA tabs keyboard pattern, matching `<Tabs>`: arrows move and
40
+ // wrap, Home and End jump to the ends, and focus follows selection.
41
+ tab.addEventListener("keydown", (event) => {
42
+ const last = tabs.length - 1;
43
+ let next: number | undefined;
44
+ if (event.key === "ArrowRight") {
45
+ next = index === last ? 0 : index + 1;
46
+ } else if (event.key === "ArrowLeft") {
47
+ next = index === 0 ? last : index - 1;
48
+ } else if (event.key === "Home") {
49
+ next = 0;
50
+ } else if (event.key === "End") {
51
+ next = last;
52
+ }
53
+ const target = next === undefined ? undefined : tabs[next];
54
+ const key = target?.dataset.panelTab;
55
+ if (!(target && key)) {
56
+ return;
57
+ }
58
+ event.preventDefault();
59
+ activate(key);
60
+ target.focus();
61
+ });
38
62
  }
39
63
 
40
64
  if (copy) {
@@ -155,6 +155,36 @@ const prettyBody = (text: string): string => {
155
155
  }
156
156
  };
157
157
 
158
+ /*
159
+ * Remembered credentials are a convenience, never a requirement. With storage
160
+ * blocked (Safari's "Block All Cookies", a sandboxed iframe) even reading the
161
+ * `localStorage` global throws a SecurityError, which would take the whole
162
+ * panel down during init; each access is guarded so the form simply forgets.
163
+ */
164
+ const readStored = (key: string): string | null => {
165
+ try {
166
+ return localStorage.getItem(key);
167
+ } catch {
168
+ return null;
169
+ }
170
+ };
171
+
172
+ const writeStored = (key: string, value: string): void => {
173
+ try {
174
+ localStorage.setItem(key, value);
175
+ } catch {
176
+ // Blocked or full storage: the credentials stay in the form only.
177
+ }
178
+ };
179
+
180
+ const removeStored = (key: string): void => {
181
+ try {
182
+ localStorage.removeItem(key);
183
+ } catch {
184
+ // Blocked storage never held the entry in the first place.
185
+ }
186
+ };
187
+
158
188
  /** A one-line text element for the response/error regions. */
159
189
  const line = (className: string, text: string): HTMLElement => {
160
190
  const el = document.createElement("div");
@@ -316,7 +346,7 @@ export const initPlayground = (root: HTMLElement): void => {
316
346
 
317
347
  /** Restore remembered credentials into the inputs and re-check the box. */
318
348
  const restoreAuth = (): void => {
319
- const stored = localStorage.getItem(storageKey);
349
+ const stored = readStored(storageKey);
320
350
  if (!stored) {
321
351
  return;
322
352
  }
@@ -355,7 +385,7 @@ export const initPlayground = (root: HTMLElement): void => {
355
385
  }
356
386
  } catch {
357
387
  // A corrupt entry (older format, manual edit) must not break init.
358
- localStorage.removeItem(storageKey);
388
+ removeStored(storageKey);
359
389
  }
360
390
  };
361
391
 
@@ -374,12 +404,12 @@ export const initPlayground = (root: HTMLElement): void => {
374
404
  )
375
405
  );
376
406
  const table = document.createElement("table");
377
- table.className = "w-full text-left text-xs";
407
+ table.className = "w-full text-start text-xs";
378
408
  for (const [name, value] of res.headers) {
379
409
  const row = document.createElement("tr");
380
410
  const header = document.createElement("th");
381
411
  header.setAttribute("scope", "row");
382
- header.className = "pr-3 align-top font-medium text-muted-foreground";
412
+ header.className = "pe-3 align-top font-medium text-muted-foreground";
383
413
  header.textContent = name;
384
414
  const cell = document.createElement("td");
385
415
  cell.className = "break-all font-mono text-foreground";
@@ -548,9 +578,9 @@ export const initPlayground = (root: HTMLElement): void => {
548
578
  }
549
579
  if (remember && target === remember) {
550
580
  if (remember.checked) {
551
- localStorage.setItem(storageKey, JSON.stringify(collectAuth()));
581
+ writeStored(storageKey, JSON.stringify(collectAuth()));
552
582
  } else {
553
- localStorage.removeItem(storageKey);
583
+ removeStored(storageKey);
554
584
  }
555
585
  } else if (
556
586
  remember?.checked &&
@@ -558,7 +588,7 @@ export const initPlayground = (root: HTMLElement): void => {
558
588
  target.dataset.authUsername !== undefined ||
559
589
  target.dataset.authPassword !== undefined)
560
590
  ) {
561
- localStorage.setItem(storageKey, JSON.stringify(collectAuth()));
591
+ writeStored(storageKey, JSON.stringify(collectAuth()));
562
592
  }
563
593
  syncSamples();
564
594
  };
@@ -1,4 +1,4 @@
1
- import { resolveSchema } from "./helpers.ts";
1
+ import { isNullable, resolveSchema } from "./helpers.ts";
2
2
  import type { SchemaLike, SpecValue } from "./helpers.ts";
3
3
  import type { ValidationSchema } from "./request.ts";
4
4
 
@@ -50,6 +50,14 @@ export const inputValue = (value: SpecValue): string => {
50
50
  /** Recursion limit for pruned validation schemas — deep enough for real specs. */
51
51
  const MAX_SCHEMA_DEPTH = 6;
52
52
 
53
+ /** Whether a property is `readOnly`, on the property or the schema it refs. */
54
+ const isReadOnly = (
55
+ property: SchemaLike | undefined,
56
+ schemas: Record<string, SchemaLike>
57
+ ): boolean =>
58
+ property?.readOnly === true ||
59
+ resolveSchema(schemas, property).readOnly === true;
60
+
53
61
  /**
54
62
  * Prune a spec schema into the tiny subset `validate-json.ts` understands:
55
63
  * `$ref`s resolved inline, cycles cut (the visited set is copied per branch so
@@ -74,15 +82,26 @@ const pruneSchema = (
74
82
  }
75
83
  const resolved = resolveSchema(schemas, schema);
76
84
  const out: ValidationSchema = {};
77
- const [type] = declaredTypes(resolved.type);
78
- if (type) {
79
- out.type = type;
85
+ // Only a single declared type is checked: a 3.1 type array like
86
+ // `["string", "integer"]` accepts either, which one `type` can't say.
87
+ const types = declaredTypes(resolved.type);
88
+ if (types.length === 1) {
89
+ [out.type] = types;
90
+ }
91
+ if (isNullable(resolved)) {
92
+ out.nullable = true;
80
93
  }
81
94
  if (resolved.enum) {
82
95
  out.enum = resolved.enum;
83
96
  }
84
- if (resolved.required && resolved.required.length > 0) {
85
- out.required = resolved.required;
97
+ // A `readOnly` property's `required` binds responses only: the server
98
+ // generates it, so a request body — which the prefilled example already
99
+ // leaves it out of — must not be told it is missing.
100
+ const required = (resolved.required ?? []).filter(
101
+ (name) => !isReadOnly(resolved.properties?.[name], schemas)
102
+ );
103
+ if (required.length > 0) {
104
+ out.required = required;
86
105
  }
87
106
  if (resolved.properties) {
88
107
  const properties: Record<string, ValidationSchema> = {};
@@ -37,6 +37,8 @@ export interface PlaygroundBodyField {
37
37
  /** A pruned, cycle-free JSON-schema subset `validate-json.ts` understands. */
38
38
  export interface ValidationSchema {
39
39
  type?: string;
40
+ /** `null` is also accepted: 3.0 `nullable`, or `"null"` in a 3.1 type array. */
41
+ nullable?: boolean;
40
42
  properties?: Record<string, ValidationSchema>;
41
43
  required?: string[];
42
44
  items?: ValidationSchema;