blume 2.1.0 → 2.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +110 -0
- package/dist/cli/{chunk-ga0pf4aj.js → chunk-0n9xkp3d.js} +12 -3
- package/dist/cli/{chunk-ga0pf4aj.js.map → chunk-0n9xkp3d.js.map} +3 -3
- package/dist/cli/{chunk-43zcf4gz.js → chunk-0pmb4tr4.js} +541 -368
- package/dist/cli/chunk-0pmb4tr4.js.map +35 -0
- package/dist/cli/{chunk-9geymqns.js → chunk-0w6hs479.js} +15 -15
- package/dist/cli/{chunk-xkz5xbtv.js → chunk-124fvmzq.js} +2802 -1422
- package/dist/cli/chunk-124fvmzq.js.map +220 -0
- package/dist/cli/chunk-150jk7km.js +52 -0
- package/dist/cli/chunk-150jk7km.js.map +10 -0
- package/dist/cli/{chunk-y4ykbark.js → chunk-1f882900.js} +2 -2
- package/dist/cli/{chunk-5ffywsmb.js → chunk-297ajdcv.js} +8 -6
- package/dist/cli/{chunk-5ffywsmb.js.map → chunk-297ajdcv.js.map} +3 -3
- package/dist/cli/{chunk-h2a48yhd.js → chunk-32khwcb6.js} +120 -16
- package/dist/cli/{chunk-h2a48yhd.js.map → chunk-32khwcb6.js.map} +10 -10
- package/dist/cli/{chunk-5cg2rzgf.js → chunk-36bxs9c7.js} +20 -49
- package/dist/cli/{chunk-5cg2rzgf.js.map → chunk-36bxs9c7.js.map} +4 -5
- package/dist/cli/{chunk-py62fewh.js → chunk-53jqw8z0.js} +3 -3
- package/dist/cli/{chunk-5wj2qwn2.js → chunk-58bt3hag.js} +4 -4
- package/dist/cli/{chunk-e7vvk5ya.js → chunk-5cmts6dv.js} +3 -3
- package/dist/cli/{chunk-y5h4r2y0.js → chunk-60hz265r.js} +15 -5
- package/dist/cli/chunk-60hz265r.js.map +10 -0
- package/dist/cli/chunk-74wfhv79.js +65 -0
- package/dist/cli/chunk-74wfhv79.js.map +10 -0
- package/dist/cli/{chunk-g2z9x01v.js → chunk-9qzgg6mj.js} +3 -3
- package/dist/cli/{chunk-q3wxxq39.js → chunk-a57yh0h9.js} +5 -5
- package/dist/cli/{chunk-713mngdq.js → chunk-ave1wcbr.js} +76 -10
- package/dist/cli/chunk-ave1wcbr.js.map +14 -0
- package/dist/cli/{chunk-2n4ps1b3.js → chunk-bqxaqn5p.js} +40 -22
- package/dist/cli/chunk-bqxaqn5p.js.map +15 -0
- package/dist/cli/{chunk-jnwj5kjj.js → chunk-bxwhtvw8.js} +20 -7
- package/dist/cli/{chunk-jnwj5kjj.js.map → chunk-bxwhtvw8.js.map} +3 -3
- package/dist/cli/chunk-ckrcm08h.js +324 -0
- package/dist/cli/chunk-ckrcm08h.js.map +14 -0
- package/dist/cli/{chunk-2f3ndkpw.js → chunk-desvm72z.js} +3 -3
- package/dist/cli/{chunk-gp84za0h.js → chunk-dhwtqnnd.js} +2 -2
- package/dist/cli/{chunk-4h8z9vv5.js → chunk-dpy8qdjf.js} +93 -47
- package/dist/cli/chunk-dpy8qdjf.js.map +17 -0
- package/dist/cli/{chunk-2f7n4qzt.js → chunk-dqhg54mr.js} +1 -1
- package/dist/cli/{chunk-0gz36akr.js → chunk-dv2z0rzf.js} +21 -5
- package/dist/cli/chunk-dv2z0rzf.js.map +12 -0
- package/dist/cli/{chunk-cm89scdv.js → chunk-es8h0v3j.js} +9 -11
- package/dist/cli/{chunk-cm89scdv.js.map → chunk-es8h0v3j.js.map} +3 -3
- package/dist/cli/{chunk-zhvd6adm.js → chunk-gfc5fqvf.js} +14 -14
- package/dist/cli/{chunk-nd1821sk.js → chunk-gxtg6ed2.js} +33 -19
- package/dist/cli/chunk-gxtg6ed2.js.map +10 -0
- package/dist/cli/{chunk-p804f3pe.js → chunk-hvcxng5f.js} +6 -6
- package/dist/cli/{chunk-ta27feev.js → chunk-j1x92mx9.js} +21 -2
- package/dist/cli/chunk-j1x92mx9.js.map +10 -0
- package/dist/cli/{chunk-rekqz4qg.js → chunk-k3gr1nm9.js} +19 -16
- package/dist/cli/chunk-k3gr1nm9.js.map +10 -0
- package/dist/cli/{chunk-1n228xqb.js → chunk-kdpzw38a.js} +2 -2
- package/dist/cli/{chunk-cy5bm0j0.js → chunk-ms9z66nz.js} +2 -2
- package/dist/cli/{chunk-cy5bm0j0.js.map → chunk-ms9z66nz.js.map} +2 -2
- package/dist/cli/{chunk-zf748avv.js → chunk-nbyvgnnm.js} +193 -286
- package/dist/cli/chunk-nbyvgnnm.js.map +36 -0
- package/dist/cli/{chunk-6bm0xpgd.js → chunk-s0bdnc7v.js} +2 -2
- package/dist/cli/{chunk-yxmk4pec.js → chunk-sc93apfs.js} +17 -16
- package/dist/cli/{chunk-yxmk4pec.js.map → chunk-sc93apfs.js.map} +3 -3
- package/dist/cli/{chunk-cmtcnh9f.js → chunk-tfycddbt.js} +2 -2
- package/dist/cli/{chunk-9xr4dgee.js → chunk-twy7psdz.js} +29 -16
- package/dist/cli/chunk-twy7psdz.js.map +10 -0
- package/dist/cli/{chunk-ntv4qrt7.js → chunk-y4msrwm9.js} +15 -15
- package/dist/cli/{chunk-yj17vs39.js → chunk-ydp3065p.js} +5 -5
- package/dist/cli/index.js +24 -19
- package/dist/cli/index.js.map +3 -3
- package/dist/types/ai/ask-context.d.ts +6 -2
- package/dist/types/ai/ask.d.ts +24 -5
- package/dist/types/ai/component-markdown.d.ts +2 -0
- package/dist/types/ai/openapi-components.d.ts +4 -1
- package/dist/types/analytics/clics.d.ts +37 -0
- package/dist/types/analytics/index.d.ts +2 -0
- package/dist/types/analytics/schema.d.ts +18 -0
- package/dist/types/astro/integration.d.ts +11 -4
- package/dist/types/astro/markdown-negotiation.d.ts +3 -0
- package/dist/types/astro/pages.d.ts +10 -0
- package/dist/types/cli/init/scaffold.d.ts +1 -1
- package/dist/types/components/content/api-field.d.ts +4 -1
- package/dist/types/components/content/api-page.d.ts +6 -1
- package/dist/types/components/openapi/code-samples.d.ts +29 -0
- package/dist/types/components/openapi/helpers.d.ts +22 -1
- package/dist/types/components/openapi/security.d.ts +10 -0
- package/dist/types/components/openapi/snippets.d.ts +60 -0
- package/dist/types/core/changelog-index.d.ts +8 -0
- package/dist/types/core/config-input.d.ts +37 -6
- package/dist/types/core/data.d.ts +4 -0
- package/dist/types/core/i18n-ui.d.ts +2 -0
- package/dist/types/core/links.d.ts +8 -0
- package/dist/types/core/powered-by.d.ts +12 -0
- package/dist/types/core/probe.d.ts +2 -0
- package/dist/types/core/project-graph.d.ts +6 -0
- package/dist/types/core/redirect-patterns.d.ts +11 -3
- package/dist/types/core/schema.d.ts +228 -3
- package/dist/types/core/sources/cache.d.ts +3 -0
- package/dist/types/core/sources/contentful-rich-text.d.ts +11 -2
- package/dist/types/core/sources/contentful.d.ts +17 -1
- package/dist/types/core/sources/lexical.d.ts +5 -0
- package/dist/types/core/sources/lower.d.ts +10 -2
- package/dist/types/core/sources/normalize.d.ts +20 -1
- package/dist/types/core/sources/notion.d.ts +8 -0
- package/dist/types/core/sources/remote.d.ts +37 -10
- package/dist/types/core/sources/sanity.d.ts +1 -1
- package/dist/types/core/types.d.ts +17 -0
- package/dist/types/deploy/adapter-output.d.ts +2 -1
- package/dist/types/deploy/adapters/cloudflare.d.ts +7 -5
- package/dist/types/deploy/adapters/node.d.ts +58 -23
- package/dist/types/deploy/adapters/registry.d.ts +10 -0
- package/dist/types/deploy/adapters/types.d.ts +3 -2
- package/dist/types/deploy/cloudflare-negotiation.d.ts +5 -2
- package/dist/types/deploy/node-headers.d.ts +12 -4
- package/dist/types/deploy/platforms/cloudflare.d.ts +10 -7
- package/dist/types/deploy/platforms/node.d.ts +3 -1
- package/dist/types/deploy/platforms/types.d.ts +18 -7
- package/dist/types/deploy/platforms/vercel.d.ts +21 -2
- package/dist/types/deploy/redirects.d.ts +17 -1
- package/dist/types/deploy/sitemap.d.ts +2 -1
- package/dist/types/deploy/vercel-negotiation.d.ts +44 -21
- package/dist/types/narration/build.d.ts +34 -12
- package/dist/types/narration/provider.d.ts +96 -8
- package/dist/types/openapi/checks.d.ts +27 -0
- package/dist/types/openapi/graphql.d.ts +6 -0
- package/dist/types/openapi/model.d.ts +15 -4
- package/dist/types/openapi/parse.d.ts +3 -0
- package/dist/types/openapi/references.d.ts +16 -0
- package/dist/types/openapi/sentence.d.ts +11 -1
- package/dist/types/ratelimit/index.d.ts +1 -1
- package/dist/types/ratelimit/schema.d.ts +8 -0
- package/dist/types/ratelimit/upstash.d.ts +39 -14
- package/dist/types/reference/graphql.d.ts +76 -1
- package/dist/types/reference/index.d.ts +1 -1
- package/dist/types/reference/openapi.d.ts +2 -2
- package/dist/types/reference/options.d.ts +37 -1
- package/dist/types/reference/schema.d.ts +84 -0
- package/dist/types/search/adapters/registry.d.ts +3 -0
- package/dist/types/search/adapters/typesense.d.ts +14 -4
- package/dist/types/search/build.d.ts +3 -1
- package/dist/types/search/orama-index.d.ts +9 -9
- package/dist/types/search/sync/algolia.d.ts +5 -2
- package/dist/types/search/sync/index.d.ts +3 -1
- package/dist/types/search/sync/typesense.d.ts +11 -7
- package/dist/types/seo/jsonld.d.ts +11 -6
- package/dist/types/sources/contentful.d.ts +16 -0
- package/dist/types/sources/github-releases.d.ts +8 -1
- package/dist/types/sources/notion.d.ts +8 -0
- package/dist/types/sources/payload.d.ts +2 -0
- package/dist/types/sources/registry.d.ts +63 -0
- package/dist/types/sources/shared.d.ts +3 -0
- package/dist/types/sources/strapi.d.ts +2 -0
- package/docs/01-quickstart.mdx +4 -2
- package/docs/02-deployment.mdx +59 -13
- package/docs/03-upgrading.mdx +2 -1
- package/docs/04-migrating.mdx +9 -9
- package/docs/08-faq.mdx +12 -12
- package/docs/advanced/changelog.mdx +30 -2
- package/docs/advanced/custom-pages.mdx +1 -1
- package/docs/advanced/skills.mdx +1 -1
- package/docs/cli/audit.mdx +30 -7
- package/docs/cli/doctor.mdx +6 -2
- package/docs/cli/evals.mdx +4 -4
- package/docs/cli/translate.mdx +1 -0
- package/docs/cli/validate.mdx +4 -3
- package/docs/configuration/analytics.mdx +19 -1
- package/docs/configuration/assistant.mdx +6 -2
- package/docs/configuration/customization.mdx +4 -2
- package/docs/configuration/export.mdx +5 -3
- package/docs/configuration/index.mdx +4 -2
- package/docs/configuration/narration.mdx +43 -6
- package/docs/configuration/rate-limiting.mdx +23 -2
- package/docs/configuration/search.mdx +37 -14
- package/docs/content/components.mdx +2 -2
- package/docs/content/frontmatter.mdx +5 -3
- package/docs/content/i18n.mdx +2 -2
- package/docs/content/includes.mdx +1 -1
- package/docs/content/navigation.mdx +8 -6
- package/docs/content/sources/contentful.mdx +9 -4
- package/docs/content/sources/custom.mdx +7 -3
- package/docs/content/sources/github-releases.mdx +12 -2
- package/docs/content/sources/index.mdx +3 -3
- package/docs/content/sources/notion.mdx +17 -2
- package/docs/content/sources/obsidian.mdx +3 -1
- package/docs/content/sources/payload.mdx +5 -2
- package/docs/content/sources/remote-mdx.mdx +6 -0
- package/docs/content/sources/sanity.mdx +4 -2
- package/docs/content/sources/strapi.mdx +5 -2
- package/docs/content/syntax.mdx +2 -2
- package/docs/discoverability/agent-discovery.mdx +1 -1
- package/docs/discoverability/index.mdx +1 -1
- package/docs/discoverability/json-api.mdx +2 -0
- package/docs/discoverability/llms-txt.mdx +1 -1
- package/docs/discoverability/markdown.mdx +3 -1
- package/docs/discoverability/mcp.mdx +6 -0
- package/docs/discoverability/sitemap-and-robots.mdx +1 -1
- package/docs/discoverability/structured-data.mdx +8 -6
- package/docs/references/api-pages.mdx +1 -1
- package/docs/references/asyncapi.mdx +5 -1
- package/docs/references/graphql.mdx +15 -1
- package/docs/references/openapi.mdx +64 -8
- package/package.json +9 -9
- package/skills/blume/SKILL.md +1 -1
- package/skills/blume-migrate/SKILL.md +2 -2
- package/skills/blume-migrate/assets/{oxfmt@0.67.0.patch → oxfmt@0.71.0.patch} +7 -7
- package/skills/blume-migrate/references/docusaurus.md +4 -4
- package/skills/blume-migrate/references/fumadocs.md +17 -13
- package/skills/blume-migrate/references/monorepo.md +5 -5
- package/skills/blume-migrate/references/nextra.md +8 -5
- package/skills/blume-migrate/references/starlight.md +9 -7
- package/src/ai/ask-context.ts +33 -3
- package/src/ai/ask-limits.ts +13 -0
- package/src/ai/ask.ts +24 -6
- package/src/ai/changelog-markdown.ts +11 -6
- package/src/ai/component-markdown.ts +1 -1
- package/src/ai/cors.ts +12 -3
- package/src/ai/mcp/query.ts +14 -6
- package/src/ai/openapi-components.ts +28 -4
- package/src/ai/relative-links.ts +4 -38
- package/src/analytics/clics.ts +58 -0
- package/src/analytics/head.ts +5 -0
- package/src/analytics/index.ts +2 -0
- package/src/analytics/schema.ts +2 -0
- package/src/astro/generate.ts +91 -56
- package/src/astro/integration.ts +72 -22
- package/src/astro/markdown-negotiation.ts +3 -0
- package/src/astro/module-types.ts +3 -1
- package/src/astro/pages.ts +23 -0
- package/src/astro/render-deps.ts +10 -5
- package/src/astro/runtime-deps.ts +23 -2
- package/src/astro/templates.ts +129 -81
- package/src/audit/catalog.ts +19 -3
- package/src/audit/checks/links.ts +14 -1
- package/src/audit/checks/network.ts +110 -6
- package/src/audit/checks/redirects.ts +6 -4
- package/src/audit/checks/robots.ts +58 -2
- package/src/audit/run.ts +3 -0
- package/src/audit/types.ts +2 -0
- package/src/audit/url.ts +14 -5
- package/src/blume-modules.d.ts +5 -1
- package/src/cli/args.ts +51 -0
- package/src/cli/build-failure.ts +23 -0
- package/src/cli/commands/audit.ts +10 -1
- package/src/cli/commands/dev.ts +18 -2
- package/src/cli/commands/doctor.ts +45 -12
- package/src/cli/commands/eval.ts +36 -4
- package/src/cli/commands/init.ts +6 -4
- package/src/cli/commands/validate.ts +10 -1
- package/src/cli/init/scaffold.ts +9 -6
- package/src/cli/prepare.ts +6 -3
- package/src/cli/required-secrets.ts +3 -2
- package/src/components/content/ApiPlayground.astro +6 -2
- package/src/components/content/api-field.ts +4 -1
- package/src/components/content/api-page.ts +31 -7
- package/src/components/content/mermaid-element.ts +15 -1
- package/src/components/content/view-switcher.ts +7 -4
- package/src/components/islands/hooks.ts +27 -1
- package/src/components/layout/NavTree.astro +49 -5
- package/src/components/layout/NavTreeScript.astro +18 -0
- package/src/components/layout/PageActions.astro +139 -24
- package/src/components/layout/PageLayout.astro +13 -3
- package/src/components/layout/RootLayout.astro +27 -14
- package/src/components/layout/Search.astro +17 -3
- package/src/components/layout/TableOfContents.astro +24 -17
- package/src/components/layout/analytics-client.ts +8 -8
- package/src/components/layout/head-scripts.ts +6 -1
- package/src/components/layout/search/algolia.ts +10 -1
- package/src/components/layout/search/analytics.ts +42 -14
- package/src/components/layout/search/endpoint.ts +48 -5
- package/src/components/layout/search/pagefind.ts +114 -5
- package/src/components/layout/search/types.ts +12 -0
- package/src/components/layout/search/typesense.ts +18 -6
- package/src/components/openapi/AsyncApiOperation.astro +14 -7
- package/src/components/openapi/Authorization.astro +10 -0
- package/src/components/openapi/GraphqlOperation.astro +5 -0
- package/src/components/openapi/Operation.astro +24 -1
- package/src/components/openapi/ParametersTable.astro +8 -4
- package/src/components/openapi/Playground.astro +7 -1
- package/src/components/openapi/RequestBody.astro +19 -2
- package/src/components/openapi/Responses.astro +1 -0
- package/src/components/openapi/SchemaTable.astro +15 -3
- package/src/components/openapi/async-snippets.ts +61 -5
- package/src/components/openapi/async.ts +123 -29
- package/src/components/openapi/graphql-helpers.ts +35 -12
- package/src/components/openapi/helpers.ts +36 -2
- package/src/components/openapi/message-model.ts +54 -11
- package/src/components/openapi/message.ts +14 -4
- package/src/components/openapi/operation-model.ts +16 -7
- package/src/components/openapi/playground-client.ts +77 -58
- package/src/components/openapi/playground-schema.ts +1 -1
- package/src/components/openapi/schema-tree.ts +44 -14
- package/src/components/openapi/security.ts +10 -0
- package/src/components/openapi/snippets.ts +5 -3
- package/src/components/openapi/validate-json.ts +4 -3
- package/src/components/powered-by-middleware.ts +33 -0
- package/src/core/changelog-index.ts +31 -1
- package/src/core/component-diagnostics.ts +53 -0
- package/src/core/component-overrides.ts +32 -0
- package/src/core/config-input.ts +42 -6
- package/src/core/data.ts +2 -0
- package/src/core/diagnostics.ts +5 -0
- package/src/core/i18n-ui.ts +2 -0
- package/src/core/includes.ts +1 -1
- package/src/core/links.ts +47 -17
- package/src/core/powered-by.ts +12 -0
- package/src/core/probe.ts +26 -6
- package/src/core/project-graph.ts +59 -18
- package/src/core/redirect-patterns.ts +36 -7
- package/src/core/schema.ts +26 -6
- package/src/core/sources/cache.ts +6 -0
- package/src/core/sources/contentful-rich-text.ts +29 -6
- package/src/core/sources/contentful.ts +135 -25
- package/src/core/sources/filesystem.ts +26 -4
- package/src/core/sources/github-releases.ts +12 -1
- package/src/core/sources/lexical.ts +15 -10
- package/src/core/sources/lower.ts +19 -3
- package/src/core/sources/mdx-remote.ts +62 -10
- package/src/core/sources/normalize.ts +227 -41
- package/src/core/sources/notion.ts +34 -10
- package/src/core/sources/obsidian.ts +16 -3
- package/src/core/sources/payload.ts +38 -15
- package/src/core/sources/remote.ts +72 -15
- package/src/core/sources/resolve.ts +11 -1
- package/src/core/sources/sanity.ts +43 -25
- package/src/core/sources/strapi.ts +2 -2
- package/src/core/types.ts +18 -0
- package/src/core/ui-packs/ar.ts +1 -0
- package/src/core/ui-packs/bg.ts +1 -0
- package/src/core/ui-packs/bn.ts +1 -0
- package/src/core/ui-packs/ca.ts +1 -0
- package/src/core/ui-packs/cs.ts +1 -0
- package/src/core/ui-packs/da.ts +1 -0
- package/src/core/ui-packs/de.ts +1 -0
- package/src/core/ui-packs/el.ts +1 -0
- package/src/core/ui-packs/es.ts +1 -0
- package/src/core/ui-packs/fa.ts +1 -0
- package/src/core/ui-packs/fi.ts +1 -0
- package/src/core/ui-packs/fr.ts +1 -0
- package/src/core/ui-packs/he.ts +1 -0
- package/src/core/ui-packs/hi.ts +1 -0
- package/src/core/ui-packs/hr.ts +1 -0
- package/src/core/ui-packs/hu.ts +1 -0
- package/src/core/ui-packs/id.ts +1 -0
- package/src/core/ui-packs/it.ts +1 -0
- package/src/core/ui-packs/ja.ts +1 -0
- package/src/core/ui-packs/ko.ts +1 -0
- package/src/core/ui-packs/nl.ts +1 -0
- package/src/core/ui-packs/no.ts +1 -0
- package/src/core/ui-packs/pl.ts +1 -0
- package/src/core/ui-packs/pt-br.ts +1 -0
- package/src/core/ui-packs/pt.ts +1 -0
- package/src/core/ui-packs/ro.ts +1 -0
- package/src/core/ui-packs/ru.ts +1 -0
- package/src/core/ui-packs/sk.ts +1 -0
- package/src/core/ui-packs/sr.ts +1 -0
- package/src/core/ui-packs/sv.ts +1 -0
- package/src/core/ui-packs/th.ts +1 -0
- package/src/core/ui-packs/tr.ts +1 -0
- package/src/core/ui-packs/uk.ts +1 -0
- package/src/core/ui-packs/vi.ts +1 -0
- package/src/core/ui-packs/zh-tw.ts +1 -0
- package/src/core/ui-packs/zh.ts +1 -0
- package/src/deploy/adapter-output.ts +7 -2
- package/src/deploy/adapters/cloudflare.ts +7 -5
- package/src/deploy/adapters/node.ts +47 -9
- package/src/deploy/adapters/types.ts +7 -3
- package/src/deploy/artifacts.ts +2 -1
- package/src/deploy/cloudflare-negotiation.ts +36 -7
- package/src/deploy/headers.ts +15 -1
- package/src/deploy/node-headers.ts +32 -20
- package/src/deploy/platforms/cloudflare.ts +14 -9
- package/src/deploy/platforms/netlify.ts +2 -1
- package/src/deploy/platforms/node.ts +12 -2
- package/src/deploy/platforms/types.ts +21 -7
- package/src/deploy/platforms/vercel.ts +115 -17
- package/src/deploy/redirects.ts +66 -6
- package/src/deploy/rss.ts +3 -1
- package/src/deploy/sitemap.ts +39 -5
- package/src/deploy/vercel-negotiation.ts +325 -103
- package/src/eval/agents.ts +18 -1
- package/src/eval/prompts.ts +6 -3
- package/src/eval/report.ts +34 -13
- package/src/eval/run.ts +5 -0
- package/src/markdown/base-links.ts +15 -14
- package/src/markdown/package-commands.ts +9 -8
- package/src/markdown/relative-links.ts +3 -2
- package/src/narration/build.ts +218 -40
- package/src/narration/provider.ts +89 -9
- package/src/openapi/checks.ts +273 -0
- package/src/openapi/graphql-build.ts +21 -0
- package/src/openapi/graphql.ts +11 -0
- package/src/openapi/model.ts +15 -3
- package/src/openapi/parse.ts +127 -2
- package/src/openapi/references.ts +43 -3
- package/src/openapi/render-mdx.ts +68 -8
- package/src/openapi/sentence.ts +29 -2
- package/src/openapi/source.ts +39 -14
- package/src/ratelimit/index.ts +1 -1
- package/src/ratelimit/runtime.ts +37 -19
- package/src/ratelimit/upstash.ts +40 -10
- package/src/reference/graphql.ts +15 -1
- package/src/reference/index.ts +1 -0
- package/src/reference/openapi.ts +2 -2
- package/src/reference/options.ts +29 -1
- package/src/registry/eject.ts +8 -2
- package/src/search/adapters/locale-scope.ts +30 -0
- package/src/search/adapters/typesense.ts +14 -4
- package/src/search/build.ts +21 -3
- package/src/search/orama-index.ts +163 -31
- package/src/search/source-pages.ts +29 -0
- package/src/search/sync/algolia.ts +99 -9
- package/src/search/sync/index.ts +52 -13
- package/src/search/sync/typesense.ts +72 -27
- package/src/seo/jsonld.ts +38 -20
- package/src/sources/contentful.ts +12 -0
- package/src/sources/github-releases.ts +7 -1
- package/src/sources/notion.ts +7 -0
- package/src/sources/shared.ts +3 -0
- package/src/theme/color.ts +9 -10
- package/src/theme/entry.ts +43 -6
- package/src/translate/validate.ts +23 -1
- package/dist/cli/chunk-0gz36akr.js.map +0 -12
- package/dist/cli/chunk-2n4ps1b3.js.map +0 -15
- package/dist/cli/chunk-43zcf4gz.js.map +0 -36
- package/dist/cli/chunk-4ejxbzee.js +0 -39
- package/dist/cli/chunk-4ejxbzee.js.map +0 -10
- package/dist/cli/chunk-4h8z9vv5.js.map +0 -16
- package/dist/cli/chunk-713mngdq.js.map +0 -14
- package/dist/cli/chunk-9xr4dgee.js.map +0 -10
- package/dist/cli/chunk-amrb6hgh.js +0 -145
- package/dist/cli/chunk-amrb6hgh.js.map +0 -11
- package/dist/cli/chunk-nd1821sk.js.map +0 -10
- package/dist/cli/chunk-rekqz4qg.js.map +0 -10
- package/dist/cli/chunk-ta27feev.js.map +0 -10
- package/dist/cli/chunk-xkz5xbtv.js.map +0 -213
- package/dist/cli/chunk-y5h4r2y0.js.map +0 -10
- package/dist/cli/chunk-zf748avv.js.map +0 -37
- /package/dist/cli/{chunk-9geymqns.js.map → chunk-0w6hs479.js.map} +0 -0
- /package/dist/cli/{chunk-y4ykbark.js.map → chunk-1f882900.js.map} +0 -0
- /package/dist/cli/{chunk-py62fewh.js.map → chunk-53jqw8z0.js.map} +0 -0
- /package/dist/cli/{chunk-5wj2qwn2.js.map → chunk-58bt3hag.js.map} +0 -0
- /package/dist/cli/{chunk-e7vvk5ya.js.map → chunk-5cmts6dv.js.map} +0 -0
- /package/dist/cli/{chunk-g2z9x01v.js.map → chunk-9qzgg6mj.js.map} +0 -0
- /package/dist/cli/{chunk-q3wxxq39.js.map → chunk-a57yh0h9.js.map} +0 -0
- /package/dist/cli/{chunk-2f3ndkpw.js.map → chunk-desvm72z.js.map} +0 -0
- /package/dist/cli/{chunk-gp84za0h.js.map → chunk-dhwtqnnd.js.map} +0 -0
- /package/dist/cli/{chunk-2f7n4qzt.js.map → chunk-dqhg54mr.js.map} +0 -0
- /package/dist/cli/{chunk-zhvd6adm.js.map → chunk-gfc5fqvf.js.map} +0 -0
- /package/dist/cli/{chunk-p804f3pe.js.map → chunk-hvcxng5f.js.map} +0 -0
- /package/dist/cli/{chunk-1n228xqb.js.map → chunk-kdpzw38a.js.map} +0 -0
- /package/dist/cli/{chunk-6bm0xpgd.js.map → chunk-s0bdnc7v.js.map} +0 -0
- /package/dist/cli/{chunk-cmtcnh9f.js.map → chunk-tfycddbt.js.map} +0 -0
- /package/dist/cli/{chunk-ntv4qrt7.js.map → chunk-y4msrwm9.js.map} +0 -0
- /package/dist/cli/{chunk-yj17vs39.js.map → chunk-ydp3065p.js.map} +0 -0
|
@@ -39,6 +39,7 @@ Every identifier below — a project key, a measurement ID, a site token — is
|
|
|
39
39
|
| `amplitude()` | [Amplitude](#amplitude) | `key` |
|
|
40
40
|
| `clarity()` | [Microsoft Clarity](#microsoft-clarity) | `id` |
|
|
41
41
|
| `clearbit()` | [Clearbit](#clearbit) | `key` |
|
|
42
|
+
| `clics()` | [Clics](#clics) | `projectId` |
|
|
42
43
|
| `cloudflare()` | [Cloudflare Web Analytics](#cloudflare-web-analytics) | `token` |
|
|
43
44
|
| `databuddy()` | [Databuddy](#databuddy) | `clientId` |
|
|
44
45
|
| `fathom()` | [Fathom](#fathom) | `site` |
|
|
@@ -149,6 +150,20 @@ analytics: [
|
|
|
149
150
|
|
|
150
151
|
`domain` becomes `data-domain` and `host` picks where `/js/script.js` loads from. Any other option becomes its own `data-` attribute on the tag (`api: "/api/event"` → `data-api`). Script variants with extra behavior baked into the filename (`script.outbound-links.js`, …) are a different tag; load those with [`script()`](#custom-scripts).
|
|
151
152
|
|
|
153
|
+
## Clics
|
|
154
|
+
|
|
155
|
+
Pass the **project ID** from Configure → Tracking in the [Clics dashboard](https://platform.clics.dev) to `clics()`. Blume adds the [Clics browser tracker](https://docs.clics.dev/installation-guides) with the required `data-project-id` attribute.
|
|
156
|
+
|
|
157
|
+
```ts blume.config.ts lineNumbers
|
|
158
|
+
import { clics } from "blume/analytics";
|
|
159
|
+
|
|
160
|
+
analytics: [clics({ projectId: "YOUR_PROJECT_ID" })],
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
The tracker counts page loads and client-side navigations automatically, including Blume's client router, so don't add a second pageview hook. It also tracks outbound links by default. Set `disableOutboundLinks: true` to turn those events off. For a production build previewed on localhost, set `allowLocalhost: true`; ordinary `blume dev` does not load analytics.
|
|
164
|
+
|
|
165
|
+
Blume's [custom events](#custom-events) don't reach Clics: its browser tracker has no global event function to forward them to. To send your own events, call the [`track()` helper](https://docs.clics.dev/custom-events) from `@clicsdev/tracker` in your own client code.
|
|
166
|
+
|
|
152
167
|
## Fathom
|
|
153
168
|
|
|
154
169
|
Pass your **site ID** (from the site's script settings in [Fathom](https://usefathom.com)) to `fathom()`.
|
|
@@ -387,6 +402,9 @@ analytics: [
|
|
|
387
402
|
| `amplitude()` | anything else | `autocapture: true`, `fetchRemoteConfig: true` | Merged into the `amplitude.init` options verbatim. |
|
|
388
403
|
| `clarity()` | `id` | — | Clarity project ID. Required. |
|
|
389
404
|
| `clearbit()` | `key` | — | Clearbit publishable API key. Required. |
|
|
405
|
+
| `clics()` | `projectId` | — | Public Clics project ID. Required. |
|
|
406
|
+
| `clics()` | `allowLocalhost` | `false` | Emit `data-allow-localhost` for local previews of a production build. |
|
|
407
|
+
| `clics()` | `disableOutboundLinks` | `false` | Emit `data-disable-outbound-links` to stop outbound-link tracking. |
|
|
390
408
|
| `cloudflare()` | `token` | — | Cloudflare Web Analytics site token (manual setup). Required. |
|
|
391
409
|
| `cloudflare()` | anything else | — | Forwarded in the beacon's `data-cf-beacon` JSON verbatim. |
|
|
392
410
|
| `databuddy()` | `clientId` | — | Databuddy client ID. Required. |
|
|
@@ -431,4 +449,4 @@ analytics: [
|
|
|
431
449
|
|
|
432
450
|
## Custom events
|
|
433
451
|
|
|
434
|
-
Blume's page feedback (`feedback`, and `feedback_comment` when [written feedback](/docs/configuration#written-feedback) is on), [search](/docs/configuration/search#search-analytics) (`search`, `search_select`), and [narration](/docs/configuration/narration#analytics) player (`narration_play`, `narration_complete`) send custom events through every configured adapter that has a client API — PostHog, Mixpanel, Heap, Segment, Hightouch, Amplitude, LogRocket, Adobe, Google Analytics, Google Tag Manager (as a `{ event }` push on `window.dataLayer`), Plausible, Databuddy, Fathom, OneDollarStats (with each property value as a string), Pirsch, Clarity, Hotjar, and Vercel. Cloudflare and Clearbit have no event API. Every event also fires as a `blume:track` CustomEvent on `window` with `{ event, props }` in `detail`, so a `script()` adapter can forward it anywhere else. For deploying your built site, see [Deployment](/docs/deployment).
|
|
452
|
+
Blume's page feedback (`feedback`, and `feedback_comment` when [written feedback](/docs/configuration#written-feedback) is on), [search](/docs/configuration/search#search-analytics) (`search`, `search_select`), [assistant](/docs/configuration/assistant#analytics) (`ask`, `ask_answer`, `ask_error`), and [narration](/docs/configuration/narration#analytics) player (`narration_play`, `narration_complete`) send custom events through every configured adapter that has a client API — PostHog, Mixpanel, Heap, Segment, Hightouch, Amplitude, LogRocket, Adobe, Google Analytics, Google Tag Manager (as a `{ event }` push on `window.dataLayer`), Plausible, Databuddy, Fathom, OneDollarStats (with each property value as a string), Pirsch, Clarity, Hotjar, and Vercel. Fathom, Clarity, and Hotjar take an event's name alone, so they count each event without its properties. Cloudflare and Clearbit have no event API, and Clics has no global one for Blume to call. Every event also fires as a `blume:track` CustomEvent on `window` with `{ event, props }` in `detail`, so a `script()` adapter can forward it anywhere else. The assistant never sends a question's text to a provider, and search sends its queries as typed unless you set [`search.analytics.queries`](/docs/configuration/search#search-analytics) to `false`, which sends each query's length instead; either way the text still reaches the `blume:track` event, for a listener you write to forward. For deploying your built site, see [Deployment](/docs/deployment).
|
|
@@ -75,7 +75,7 @@ Your text is **appended to** the built-in instructions rather than replacing the
|
|
|
75
75
|
|
|
76
76
|
The assistant is **grounded in your docs**. For each question it retrieves the most relevant pages — using the same lexical [Orama](/docs/configuration/search) index that powers on-page search — and injects them into the model's system prompt, so answers come from your content instead of the model's own knowledge. The assistant is told to answer only from the retrieved pages, to say when something isn't covered, and to cite the pages it drew from.
|
|
77
77
|
|
|
78
|
-
The page the reader is currently on is added to the context first and used to scope retrieval to that page's language — and, on a [versioned](/docs/content/versioning) site, to its docs version — so answers stay relevant to where they are in the docs. Retrieval runs at request time from a snapshot baked into the build, so it works regardless of your [search](/docs/configuration/search) provider — even with `search: false` — and needs no configuration.
|
|
78
|
+
The page the reader is currently on is added to the context first and used to scope retrieval to that page's language — and, on a [versioned](/docs/content/versioning) site, to its docs version — so answers stay relevant to where they are in the docs. A question from outside the docs, such as a landing page, keeps to the language the URL's locale prefix names (`/ja`), or to the default language. Retrieval runs at request time from a snapshot baked into the build, so it works regardless of your [search](/docs/configuration/search) provider — even with `search: false` — and needs no configuration.
|
|
79
79
|
|
|
80
80
|
Grounding is on for every adapter except **[Inkeep](#inkeep)**, which runs its own retrieval over the content you've indexed in its dashboard.
|
|
81
81
|
|
|
@@ -124,6 +124,8 @@ The three aren't interchangeable. `contextBudget` caps the whole injection, `exc
|
|
|
124
124
|
|
|
125
125
|
The defaults suit a hosted model. Lower them when you're serving from your own hardware and time-to-first-token matters more than recall; answers stay grounded either way, and the assistant is told to say when something isn't covered rather than fill the gap.
|
|
126
126
|
|
|
127
|
+
On your own hardware, also size the model's context window for the whole prompt, not only the excerpts: the conversation so far adds as many as 24,000 characters, and with `tools` on, one `read_page` result as many as 20,000, about 5,000 tokens on its own. Ollama, for one, defaults to a 4k-token window on smaller GPUs, and a prompt that outgrows it doesn't fail: Ollama drops the start of the prompt, where the instructions and excerpts are, and the model answers without your docs.
|
|
128
|
+
|
|
127
129
|
## External endpoint
|
|
128
130
|
|
|
129
131
|
Already have an API backend for AI? Point the panel at it and keep the docs build static:
|
|
@@ -146,6 +148,8 @@ Blume sends the same `POST` body as its built-in route:
|
|
|
146
148
|
}
|
|
147
149
|
```
|
|
148
150
|
|
|
151
|
+
`messages` is the conversation so far, ending with the new question. The built-in route takes at most 40 messages and 24,000 characters of them as JSON, so once a conversation outgrows either, the panel sends its latest turns that fit and drops the oldest.
|
|
152
|
+
|
|
149
153
|
Return a successful response whose body is a plain UTF-8 text stream. If the endpoint is on another origin, allow the docs origin with CORS: accept `OPTIONS` and `POST`, permit the `content-type` request header, and return the CORS headers on both the preflight and streamed response. With `endpoint` set, Blume generates the chat UI but no server route, grounding snapshot, provider dependency, or provider-secret warning; your backend owns retrieval, authentication, rate limiting, model access, and citations. An adapter set alongside it is ignored.
|
|
150
154
|
|
|
151
155
|
## Cross-origin callers
|
|
@@ -161,7 +165,7 @@ ai: {
|
|
|
161
165
|
}
|
|
162
166
|
```
|
|
163
167
|
|
|
164
|
-
The route then answers the browser's `OPTIONS` preflight and names a listed origin on every response — the streamed answer and the error statuses alike, so the caller can tell a rejected body from a provider failure. Origins that aren't listed get no header and stay subject to the browser's same-origin rule. Each entry is reduced to its origin, so `https://www.example.com/docs/` and `https://www.example.com` mean the same thing. To let any page call the route, list `"*"` instead of origins.
|
|
168
|
+
The route then answers the browser's `OPTIONS` preflight and names a listed origin on every response — the streamed answer and the error statuses alike, so the caller can tell a rejected body from a provider failure. Those responses expose `Retry-After` too, so after a [rate limit's](/docs/configuration/rate-limiting) `429` the caller can read how many seconds to wait. Origins that aren't listed get no header and stay subject to the browser's same-origin rule. Each entry is reduced to its origin, so `https://www.example.com/docs/` and `https://www.example.com` mean the same thing. To let any page call the route, list `"*"` instead of origins.
|
|
165
169
|
|
|
166
170
|
The caller sends the same `POST` body the [external endpoint](#external-endpoint) contract describes and reads back the same text stream. Send it as JSON with a `content-type: application/json` header:
|
|
167
171
|
|
|
@@ -84,7 +84,7 @@ export default defineComponents({
|
|
|
84
84
|
});
|
|
85
85
|
```
|
|
86
86
|
|
|
87
|
-
Wired slots:
|
|
87
|
+
Wired slots (names are case-sensitive; any other key under `layout` renders nowhere, and Blume warns about it):
|
|
88
88
|
|
|
89
89
|
| Slot | Replaces | Props |
|
|
90
90
|
| --- | --- | --- |
|
|
@@ -95,13 +95,15 @@ Wired slots:
|
|
|
95
95
|
| `Sidebar` | The primary navigation tree | `items`, `currentRoute` |
|
|
96
96
|
| `MobileNav` | The nav inside the mobile drawer (defaults to `Sidebar`) | `items`, `currentRoute` |
|
|
97
97
|
| `Breadcrumbs` | The breadcrumb trail | `crumbs` |
|
|
98
|
-
| `TableOfContents` | The on-this-page outline | `headings`, `title`, `variant` |
|
|
98
|
+
| `TableOfContents` | The on-this-page outline, in the right rail (`variant: "desktop"`) and in the dropdown above the content on narrower screens (`"mobile"`) | `headings`, `title`, `variant` |
|
|
99
99
|
| `Pagination` | The prev/next footer links | `prev`, `next`, `strings` |
|
|
100
100
|
| `Feedback` | The "Was this page helpful?" rating below the article (rendered only when [`feedback`](/docs/configuration#page-feedback) is on) | `strings`, `comments` |
|
|
101
101
|
| `PageHeader` | An injection point above the article (no built-in) | `page`, `headings`, `route` |
|
|
102
102
|
| `PageFooter` | An injection point below the article (no built-in) | `page`, `headings`, `route` |
|
|
103
103
|
| `Footer` | The [site footer](/docs/configuration#footer) after the content grid: links, social profiles, the repository link, and Cookie settings | `footer`, `locale`, `site`, `navigation`, `ui` |
|
|
104
104
|
|
|
105
|
+
The `mobile` variant also receives the [page actions](/docs/content/navigation#page-actions) as its children (an empty element the actions move into from the right rail below 1,280px), so render a `<slot />` in it to keep them on narrower screens. It renders on a page with no headings as well, with `headings` empty, so the actions still show there.
|
|
106
|
+
|
|
105
107
|
`PageHeader` and `PageFooter` have no built-in component — they render nothing until you set them, which makes them handy injection points for a promo banner or a "last updated" note. The built-in `Footer` holds the only links to your repository and social profiles, so a `Footer` override (the place for a marketing footer the config can't express) should keep them. With `consent` set, also keep a way to reopen it: any element with `data-blume-consent-open` works as the [Cookie settings](/docs/configuration/consent#changing-an-answer) link.
|
|
106
108
|
|
|
107
109
|
Layout slots accept the same [three reference forms](#reference-form) as MDX overrides, so a slot can be a path string or a hydrated descriptor (`{ component, client }`) when you want an interactive header or footer.
|
|
@@ -26,7 +26,7 @@ Any format you omit (or set to `false`) is left out of the menu. With both off
|
|
|
26
26
|
|
|
27
27
|
## PDF
|
|
28
28
|
|
|
29
|
-
**Export to PDF** opens your browser's print dialog with a print stylesheet that strips the site chrome — header, sidebars, navigation — down to just the article. Choose **Save as PDF** to finish.
|
|
29
|
+
**Export to PDF** opens your browser's print dialog with a print stylesheet that strips the site chrome — header, sidebars, navigation, and everything after the article (last updated, feedback, previous and next links, the site footer), plus the copy buttons on code blocks — down to just the article. Collapsed [`expandable`](/docs/content/syntax#long-lines-and-long-blocks) code blocks print in full, and a page read in the dark theme prints in the light one. Choose **Save as PDF** to finish. The same stylesheet applies when a reader prints from the browser's own menu.
|
|
30
30
|
|
|
31
31
|
Because it prints the live, fully-styled page, the result keeps crisp, selectable text, real fonts, and syntax-highlighted code. It needs no dependencies and works the same in dev, in production, and on static hosts.
|
|
32
32
|
|
|
@@ -38,11 +38,13 @@ PDF export goes through the browser's native print dialog, so the exact "Save as
|
|
|
38
38
|
|
|
39
39
|
**Export to EPUB** generates a self-contained `.epub` of the current page in the browser and downloads it — ready for Apple Books, Calibre, or any e-reader. The generator is loaded only when a reader clicks Export, so it never weighs down the rest of your site.
|
|
40
40
|
|
|
41
|
-
To keep the file readable on a device with no stylesheet or JavaScript of its own, Blume rewrites the page into clean, standalone HTML: syntax-highlighted code becomes plain monospace, decorative icons and copy buttons are dropped, and a built-in e-reader stylesheet handles spacing, code blocks, and tables.
|
|
41
|
+
To keep the file readable on a device with no stylesheet or JavaScript of its own, Blume rewrites the page into clean, standalone HTML: syntax-highlighted code becomes plain monospace, decorative icons and copy buttons are dropped, and a built-in e-reader stylesheet handles spacing, code blocks, and tables. Links to other pages point at your live site, since the book holds only this page.
|
|
42
|
+
|
|
43
|
+
The book is titled after the page's heading, takes the page's language, and names your site's [`title`](/docs/configuration#site) as its author and publisher. Images are fetched into the book; one the browser can't fetch, such as an image on another host that doesn't allow cross-origin requests, is left out, with a warning in the browser console.
|
|
42
44
|
|
|
43
45
|
## What gets exported
|
|
44
46
|
|
|
45
|
-
Both formats export the **single page** a reader is viewing — not the whole site — which matches where the action lives, beneath that page's table of contents.
|
|
47
|
+
Both formats export the **single page** a reader is viewing — not the whole site — which matches where the action lives, beneath that page's table of contents. Like the other [page actions](/docs/content/navigation#page-actions), it sits in the right-hand column in windows at least 1,280px wide, and in the “On this page” dropdown above the content in narrower windows and on phones.
|
|
46
48
|
|
|
47
49
|
:::note
|
|
48
50
|
In an EPUB there's no JavaScript to switch tabs, so tabbed content — like [package-install](/docs/content/syntax) blocks and code groups — is expanded to show every panel at once. Client-rendered embeds such as Mermaid diagrams aren't included.
|
|
@@ -459,7 +459,7 @@ export default defineConfig({
|
|
|
459
459
|
});
|
|
460
460
|
```
|
|
461
461
|
|
|
462
|
-
A reader's answer is sent as a `feedback` [custom event](/docs/configuration/analytics#custom-events) — with `helpful` (`"yes"` or `"no"`), `path`, and `title` — through every configured analytics adapter that has an event API, and as a `blume:track` event on `window`. Without an analytics adapter, the answer isn't recorded anywhere, and with [cookie consent](/docs/configuration/consent) on, the rating only shows for readers who allowed analytics, since from anyone else an answer would go nowhere. The question and thank-you text are UI strings you can translate with [`i18n.ui`](/docs/content/i18n#translated-ui).
|
|
462
|
+
A reader's answer is sent as a `feedback` [custom event](/docs/configuration/analytics#custom-events) — with `helpful` (`"yes"` or `"no"`), `path`, and `title` — through every configured analytics adapter that has an event API, and as a `blume:track` event on `window`. Fathom, Microsoft Clarity, and Hotjar take an event's name alone, without its properties, so they count ratings but can't tell a yes from a no. Without an analytics adapter, the answer isn't recorded anywhere, and with [cookie consent](/docs/configuration/consent) on, the rating only shows for readers who allowed analytics, since from anyone else an answer would go nowhere. The question and thank-you text are UI strings you can translate with [`i18n.ui`](/docs/content/i18n#translated-ui).
|
|
463
463
|
|
|
464
464
|
### Written feedback
|
|
465
465
|
|
|
@@ -471,7 +471,7 @@ export default defineConfig({
|
|
|
471
471
|
});
|
|
472
472
|
```
|
|
473
473
|
|
|
474
|
-
A comment is sent as a `feedback_comment` event with the same `helpful`, `path`, and `title` as the rating, plus the reader's `comment` (up to 1,000 characters). It goes through the same adapters as the rating, so read comments where you read events. Providers cap how long a property value can be, so a long comment may arrive
|
|
474
|
+
A comment is sent as a `feedback_comment` event with the same `helpful`, `path`, and `title` as the rating, plus the reader's `comment` (up to 1,000 characters). It goes through the same adapters as the rating, so read comments where you read events. Providers cap how long a property value can be, so a long comment may not arrive whole: Google Analytics keeps 100 characters, Mixpanel 255 bytes (fewer characters in a script that takes several bytes a character, like Japanese), and Vercel Web Analytics takes values up to 255 characters, while PostHog and Segment pass comments whole. Fathom, Clarity, and Hotjar get no comment text at all. Plausible groups identical values rather than listing each comment, so it suits ratings better than comments. Readers can write anything in the box, so treat comments as they might contain personal details.
|
|
475
475
|
|
|
476
476
|
The box's label and button are UI strings too (`feedback.comment` and `feedback.send`).
|
|
477
477
|
|
|
@@ -496,6 +496,7 @@ Each of these has its own guide. The config field is the entry point:
|
|
|
496
496
|
| `seo` | Metadata, OG images, feeds, structured data, sitemap, robots | [SEO and GEO](/docs/discoverability) |
|
|
497
497
|
| `deployment` | A host adapter from `blume/deploy`, or `{ site, base }` for a static build | [Deployment](/docs/deployment) |
|
|
498
498
|
| `redirects` | Permanent and temporary redirects | [Deployment](/docs/deployment#redirects) |
|
|
499
|
+
| `poweredBy` | The `X-Powered-By: Blume` response header (default `true`) | [Deployment](/docs/deployment#powered-by-header) |
|
|
499
500
|
| `integrations` | Astro integrations appended after Blume's built-ins | [Customization](/docs/configuration/customization#astro-integrations) |
|
|
500
501
|
| `basePath` | A path every generated route mounts under (e.g. `/docs`), invisible to the sidebar | [Deployment](/docs/deployment#mount-the-docs-under-a-path) |
|
|
501
502
|
| `i18n` | Locales, the default locale, and translated UI strings | [Internationalization](/docs/content/i18n) |
|
|
@@ -505,6 +506,7 @@ Each of these has its own guide. The config field is the entry point:
|
|
|
505
506
|
| `react` | React island behavior — the React Compiler's auto-memoization | [Islands](/docs/content/islands#frameworks) |
|
|
506
507
|
| `variables` | Values `{{name}}` reads in any page | [Variables](/docs/content/variables) |
|
|
507
508
|
| `feedback` | The "Was this page helpful?" rating at the foot of each page (default `true`), and optional written comments after it | [Page feedback](#page-feedback) |
|
|
509
|
+
| `changelog` | The title and description of the generated `/changelog` index, each a string or a per-locale map | [Changelogs](/docs/advanced/changelog#the-index-page) |
|
|
508
510
|
|
|
509
511
|
## Precedence
|
|
510
512
|
|
|
@@ -44,7 +44,7 @@ This needs nothing from you and costs nothing, but how it sounds depends on the
|
|
|
44
44
|
|
|
45
45
|
## Generated voices
|
|
46
46
|
|
|
47
|
-
Pass a `provider` to generate audio with a neural speech model at build time. The provider is the
|
|
47
|
+
Pass a `provider` to generate audio with a neural speech model at build time. The provider is one of the assistant's [adapters](/docs/configuration/assistant#adapters), pointed at a speech model: `gateway()` for the speech models on the Vercel AI Gateway, or [`openai()`](#openai-and-self-hosted-voices) for OpenAI's own speech models or a speech server you run yourself.
|
|
48
48
|
|
|
49
49
|
```ts blume.config.ts lineNumbers
|
|
50
50
|
import { defineConfig } from "blume";
|
|
@@ -60,7 +60,7 @@ export default defineConfig({
|
|
|
60
60
|
});
|
|
61
61
|
```
|
|
62
62
|
|
|
63
|
-
`blume build` reads each built page exactly as the player will, splits it into sentences, and generates one clip per sentence through the [Vercel AI Gateway](https://vercel.com/docs/ai-gateway). The clips and a small manifest per page are written into your build as static files under `/blume-narration/`, so replays cost nothing and nothing runs on a server.
|
|
63
|
+
`blume build` reads each built page exactly as the player will, splits it into sentences, and generates one clip per sentence through the provider, here the [Vercel AI Gateway](https://vercel.com/docs/ai-gateway). The clips and a small manifest per page are written into your build as static files under `/blume-narration/`, so replays cost nothing and nothing runs on a server.
|
|
64
64
|
|
|
65
65
|
Before it generates anything, the build logs how many new clips it needs and how many characters they cover, so the cost is visible up front:
|
|
66
66
|
|
|
@@ -68,6 +68,8 @@ Before it generates anything, the build logs how many new clips it needs and how
|
|
|
68
68
|
Generating narration: 214 new clip(s), 15,880 characters, with openai/tts-1-hd
|
|
69
69
|
```
|
|
70
70
|
|
|
71
|
+
`gateway()` takes:
|
|
72
|
+
|
|
71
73
|
| Option | Default | Description |
|
|
72
74
|
| --- | --- | --- |
|
|
73
75
|
| `model` | `openai/tts-1-hd` | A gateway speech model: `openai/tts-1`, `openai/tts-1-hd`, `fish-audio/s2.1-pro`, `spacexai/grok-tts`, and others the gateway lists. |
|
|
@@ -77,21 +79,56 @@ Generating narration: 214 new clip(s), 15,880 characters, with openai/tts-1-hd
|
|
|
77
79
|
| `headers` | | Static headers sent with every request. |
|
|
78
80
|
| `providerOptions` | | Passed to the AI SDK's `generateSpeech` as-is, for model settings Blume doesn't name. |
|
|
79
81
|
|
|
82
|
+
### OpenAI and self-hosted voices
|
|
83
|
+
|
|
84
|
+
`openai()` generates the clips with OpenAI's speech API instead: OpenAI's own models with your `OPENAI_API_KEY`, or, with a `baseUrl`, any server that serves the same `POST /audio/speech` endpoint, such as [Kokoro-FastAPI](https://github.com/remsky/Kokoro-FastAPI), [Speaches](https://speaches.ai), or a [LiteLLM](https://docs.litellm.ai) proxy with a speech model. A site whose build can't reach a hosted service gets generated voices this way:
|
|
85
|
+
|
|
86
|
+
```ts blume.config.ts lineNumbers
|
|
87
|
+
import { defineConfig } from "blume";
|
|
88
|
+
import { openai } from "blume/ai";
|
|
89
|
+
|
|
90
|
+
export default defineConfig({
|
|
91
|
+
narration: {
|
|
92
|
+
provider: openai({
|
|
93
|
+
baseUrl: "http://localhost:8880/v1",
|
|
94
|
+
model: "kokoro",
|
|
95
|
+
voice: "af_heart",
|
|
96
|
+
}),
|
|
97
|
+
},
|
|
98
|
+
});
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
A server that takes a key, like a LiteLLM proxy, gets the one you name in `apiKeyEnv` (`openai({ baseUrl, apiKeyEnv: "LITELLM_API_KEY", model })`). Without `apiKeyEnv`, a `baseUrl` server is called with no key, so your `OPENAI_API_KEY` never leaves for another server. A response that isn't MP3, like a sign-in page from a proxy in front of the server or WAV from a server that ignores the format it was asked for, counts as a failed clip rather than being cached.
|
|
102
|
+
|
|
103
|
+
It speaks through the AI SDK's OpenAI provider, with or without a `baseUrl`, so add `@ai-sdk/openai` to your project (`npm install @ai-sdk/openai`). If it's missing, `blume build` stops before Vite runs and names the package.
|
|
104
|
+
|
|
105
|
+
| Option | Default | Description |
|
|
106
|
+
| --- | --- | --- |
|
|
107
|
+
| `model` | | Required. A speech model: `gpt-4o-mini-tts`, `tts-1`, or `tts-1-hd` on OpenAI, or the id your server serves. |
|
|
108
|
+
| `baseUrl` | | The server's OpenAI-compatible base URL, including the `/v1`. Without it, the clips come from OpenAI, or from the server `OPENAI_BASE_URL` names when that's set, as with OpenAI's own SDKs. |
|
|
109
|
+
| `voice` | `alloy` | The model's voice. |
|
|
110
|
+
| `instructions` | | How the voice should sound, for models that take instructions (`gpt-4o-mini-tts`). |
|
|
111
|
+
| `apiKeyEnv` | `OPENAI_API_KEY` without a `baseUrl` | The env var holding the key, sent as a bearer token. With a `baseUrl`, only a key you name here is sent, so your `OPENAI_API_KEY` never reaches another server; name none and requests go without a bearer token (an `Authorization` you set in `headers` is still sent). |
|
|
112
|
+
| `headers` | | Static headers sent with every request. |
|
|
113
|
+
| `providerOptions` | | Passed to the AI SDK's `generateSpeech` as-is. OpenAI's speech API reads `speed` from it: `{ openai: { speed: 1.1 } }`. |
|
|
114
|
+
|
|
115
|
+
OpenAI's speech API takes no language, so `openai()` doesn't send the page's, and a sentence that appears in several languages is generated once. OpenAI's models read the language off the text, but many self-hosted servers go by the voice, so pick one that speaks your content's language.
|
|
116
|
+
|
|
80
117
|
### Caching
|
|
81
118
|
|
|
82
|
-
Clips are cached in `node_modules/.cache/blume/narration` by what decides their sound: the sentence, its language, the model, the voice, and the
|
|
119
|
+
Clips are cached in `node_modules/.cache/blume/narration` by what decides their sound: the sentence, its language (for `gateway()`, which sends it), the model, the voice, the instructions, the `providerOptions`, and, for `openai()`, the server it calls. Pointing `baseUrl` at another server regenerates every clip, even with the same model and voice. A rebuild only pays for sentences that changed, and a sentence shared by many pages is generated once. Vercel and Netlify restore `node_modules` from their build caches, so a deploy there regenerates only what changed. On other CI, cache that directory between runs.
|
|
83
120
|
|
|
84
121
|
### Fallbacks
|
|
85
122
|
|
|
86
123
|
Generated voices fall back to browser voices wherever no clips exist:
|
|
87
124
|
|
|
88
125
|
- In `blume dev`, which never generates audio. Run `blume build` and `blume preview` to hear the generated voice locally.
|
|
89
|
-
- When the key isn't set at build time. The build warns and
|
|
90
|
-
- When generation fails
|
|
126
|
+
- When the key isn't set at build time. The build warns and generates nothing new, but pages whose clips are all cached still get them. `openai()` with a `baseUrl` and no `apiKeyEnv` sends no key, so it goes ahead.
|
|
127
|
+
- When generation fails, a server the build can't reach included. Generation stops at the first failed clip, since the AI SDK has already retried it, and the build carries on with a warning, keeping every clip it has cached.
|
|
91
128
|
|
|
92
129
|
## Languages
|
|
93
130
|
|
|
94
|
-
Narration reads each page in its content language, so on an [internationalized](/docs/content/i18n) site every locale is read in its own voice. The spoken cues and the player's labels are translated in every built-in UI language. Override them per locale under `narration` in [`i18n.ui`](/docs/content/i18n#translated-ui).
|
|
131
|
+
Narration reads each page in its content language, so on an [internationalized](/docs/content/i18n) site every locale is read in its own voice. With `openai()`, that depends on the model and voice: see [OpenAI and self-hosted voices](#openai-and-self-hosted-voices). The spoken cues and the player's labels are translated in every built-in UI language. Override them per locale under `narration` in [`i18n.ui`](/docs/content/i18n#translated-ui).
|
|
95
132
|
|
|
96
133
|
## Turning it off for a page
|
|
97
134
|
|
|
@@ -5,7 +5,7 @@ description: Limit how often one reader can call the assistant, the API playgrou
|
|
|
5
5
|
|
|
6
6
|
The server routes a reader can call cost you something each time: the [assistant](/docs/configuration/assistant) spends model tokens, the [API playground](/docs/references/openapi#cors-and-the-proxy) proxy relays requests to your API, and [Mixedbread search](/docs/configuration/search#mixedbread) runs a paid query. Blume limits how often one reader, identified by IP address, can call each of them. A reader over the limit gets `429 Too Many Requests` with a `Retry-After` header, and the assistant tells them to try again in a few minutes.
|
|
7
7
|
|
|
8
|
-
Rate limiting is on by default, at 30 requests per reader per route every 10 minutes:
|
|
8
|
+
Rate limiting is on by default, at 30 requests per reader per route every 10 minutes: well above what a person sends to the assistant or the playground, and low enough to stop a script. Search counts differently. The search dialog sends a query each time the reader pauses while typing, so one search can take several requests, and a reader who searches steadily for a few minutes can reach the limit. If your site uses Mixedbread search, raise `requests`, which applies to every route. Rate limiting only matters for a server build; a static site has no server routes to limit.
|
|
9
9
|
|
|
10
10
|
```ts blume.config.ts lineNumbers
|
|
11
11
|
import { defineConfig } from "blume";
|
|
@@ -38,7 +38,7 @@ rateLimit: memory({ requests: 60, window: 600 }),
|
|
|
38
38
|
|
|
39
39
|
### Upstash
|
|
40
40
|
|
|
41
|
-
`upstash()` keeps the count in Upstash Redis, which every instance shares. Create a database
|
|
41
|
+
`upstash()` keeps the count in Upstash Redis, which every instance shares. Create a database, then set its REST endpoint and token as `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN`. Blume talks to Upstash's REST API, so there's nothing to install. Until both are set, the routes count in memory and `blume build` warns.
|
|
42
42
|
|
|
43
43
|
```ts blume.config.ts lineNumbers
|
|
44
44
|
import { upstash } from "blume/ratelimit";
|
|
@@ -46,6 +46,12 @@ import { upstash } from "blume/ratelimit";
|
|
|
46
46
|
rateLimit: upstash(),
|
|
47
47
|
```
|
|
48
48
|
|
|
49
|
+
A database added from Vercel's Marketplace sets its endpoint and token as `KV_REST_API_URL` and `KV_REST_API_TOKEN` instead, with any prefix you chose when you connected it. Name the variables with `urlEnv` and `tokenEnv`:
|
|
50
|
+
|
|
51
|
+
```ts blume.config.ts lineNumbers
|
|
52
|
+
rateLimit: upstash({ urlEnv: "KV_REST_API_URL", tokenEnv: "KV_REST_API_TOKEN" }),
|
|
53
|
+
```
|
|
54
|
+
|
|
49
55
|
### Cloudflare
|
|
50
56
|
|
|
51
57
|
`cloudflare()` counts with [Workers rate limiting](https://developers.cloudflare.com/workers/runtime-apis/bindings/rate-limit/), for a site deployed with `deployment: cloudflare()`. Blume declares the binding in the Worker's config when it builds, so there's nothing to set up. Cloudflare counts per location and takes a window of 10 or 60 seconds, so its defaults are 10 requests per 60 seconds.
|
|
@@ -62,6 +68,21 @@ export default defineConfig({
|
|
|
62
68
|
|
|
63
69
|
The binding's namespace comes from the Worker's name, so two sites on one account don't share a count. Set `namespaceId` to choose it yourself.
|
|
64
70
|
|
|
71
|
+
## Behind a reverse proxy
|
|
72
|
+
|
|
73
|
+
Vercel, Netlify, and Cloudflare tell Blume each reader's address. A `node()` server behind a reverse proxy, like nginx or Caddy, sees every request come from the proxy, so every reader shares one count and a busy site hits the limit for everyone. List the host the proxy answers for in `allowedDomains`:
|
|
74
|
+
|
|
75
|
+
```ts blume.config.ts lineNumbers
|
|
76
|
+
import { node } from "blume/deploy";
|
|
77
|
+
|
|
78
|
+
deployment: node({
|
|
79
|
+
site: "https://docs.example.com",
|
|
80
|
+
allowedDomains: [{ hostname: "docs.example.com" }],
|
|
81
|
+
}),
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
This is Astro's [`security.allowedDomains`](https://docs.astro.build/en/reference/configuration-reference/#securityalloweddomains): for a request whose `Host` or `X-Forwarded-Host` header matches an entry, Astro takes the reader's address from the first entry of `X-Forwarded-For`. Have the proxy pass the site's host through and replace any `X-Forwarded-For` the reader sent with the address it sees, like nginx's `proxy_set_header X-Forwarded-For $remote_addr;`. A proxy that appends to the header instead lets a reader pick their own address and get a fresh count with every request. Leave `allowedDomains` unset when readers reach the server directly.
|
|
85
|
+
|
|
65
86
|
## Turning it off
|
|
66
87
|
|
|
67
88
|
Set `rateLimit: false` to let every request through, when your host's firewall already limits these routes, say. A request whose address the host can't tell is always let through, and so is one a shared store fails to count: the failure is logged, and readers never get locked out by a store outage.
|
|
@@ -5,7 +5,7 @@ description: Client-side search that works out of the box with no API keys, plus
|
|
|
5
5
|
|
|
6
6
|
Blume ships local search with no hosted infrastructure and no API keys. It runs in the browser, works in both `blume dev` and `blume build`, and indexes only your real content — navigation chrome and excluded pages are skipped. When you outgrow it, you can switch to a hosted or semantic backend without changing how search looks or behaves — only the adapter you pass to `search` changes.
|
|
7
7
|
|
|
8
|
-
Every backend is an **adapter** imported from `blume/search`: **Orama** (the default), **FlexSearch**, **Pagefind**, **Algolia**, **Orama Cloud**, **Typesense**, and **Mixedbread**. Each adapter owns its options, its runtime dependency, and the secrets it needs
|
|
8
|
+
Every backend is an **adapter** imported from `blume/search`: **Orama** (the default), **FlexSearch**, **Pagefind**, **Algolia**, **Orama Cloud**, **Typesense**, and **Mixedbread**. Each adapter owns its options, its runtime dependency, and the secrets it needs. Orama and Pagefind ship with Blume; every other adapter's SDK is an optional peer dependency that you install yourself, so picking one backend never pulls in the others. If it's missing, `blume build` stops before Vite runs, naming the package and the command that installs it, and [`blume doctor`](/docs/cli/doctor) reports it too.
|
|
9
9
|
|
|
10
10
|
```ts blume.config.ts lineNumbers
|
|
11
11
|
import { defineConfig } from "blume";
|
|
@@ -20,7 +20,7 @@ export default defineConfig({
|
|
|
20
20
|
});
|
|
21
21
|
```
|
|
22
22
|
|
|
23
|
-
An adapter is a plain description of the backend — not a live client — so Blume can inline it into the generated site and into an ejected project. Pass the adapter directly, or use the object form when you also want to set the [popular links](#popular-pages)
|
|
23
|
+
An adapter is a plain description of the backend — not a live client — so Blume can inline it into the generated site and into an ejected project. Pass the adapter directly, or use the object form when you also want to set the [popular links](#popular-pages), [indexing](#whats-indexed), or [analytics](#search-analytics) options beside it:
|
|
24
24
|
|
|
25
25
|
```ts blume.config.ts lineNumbers
|
|
26
26
|
import { pagefind } from "blume/search";
|
|
@@ -77,19 +77,23 @@ search: {
|
|
|
77
77
|
},
|
|
78
78
|
```
|
|
79
79
|
|
|
80
|
-
Each fence's body and title (`blume.config.ts` above) become searchable; the language and fence markers don't. On `.mdx` pages the index reads components as the text they show — a Card's title, a Tab's label, a TypeTable's descriptions — using the same serializers as the [agent surfaces](/docs/discoverability/markdown), so an `agents.markdownComponents` entry covers your own components too. The option has no effect on Pagefind or Mixedbread. Expect the index to grow with your fenced content — the client index ships to every reader, hosted adapters
|
|
80
|
+
Each fence's body and title (`blume.config.ts` above) become searchable; the language and fence markers don't. On `.mdx` pages the index reads components as the text they show — a Card's title, a Tab's label, a TypeTable's descriptions — using the same serializers as the [agent surfaces](/docs/discoverability/markdown), so an `agents.markdownComponents` entry covers your own components too. The option has no effect on Pagefind or Mixedbread. Expect the index to grow with your fenced content — the client index ships to every reader, hosted adapters store more (the [Algolia](#algolia) sync splits a long page across several records), and a hit inside a fence shows flattened code in the result excerpt.
|
|
81
81
|
|
|
82
82
|
On a [versioned](/docs/content/versioning) site, results default to the version being viewed, with an "All versions" toggle in the dialog footer (remembered per reader). Cross-version hits name their version on the row. Orama, FlexSearch, Algolia, and Typesense honor the scoping — hosted records carry a `version` facet, with the current docs uploaded as `"current"`. Pagefind, Orama Cloud, and Mixedbread don't scope by version: their results span every version, and the dialog leaves out the toggle.
|
|
83
83
|
|
|
84
|
+
On a site with [`i18n`](/docs/content/i18n), results default to the language of the page being viewed, with an "All languages" toggle beside it (also remembered per reader). Orama and FlexSearch filter their index in the browser, and Algolia, Orama Cloud, and Typesense filter on the `locale` each record carries. Pagefind keeps an index per language: it searches the page's own, and with "All languages" it merges in the others, which it then searches with the page language's word stemming. Mixedbread doesn't scope by language: its results span every language, and the dialog leaves out the toggle.
|
|
85
|
+
|
|
84
86
|
## Tags
|
|
85
87
|
|
|
86
|
-
Add `search.tags` to a page's frontmatter to
|
|
88
|
+
Add `search.tags` to a page's frontmatter to label it for a hosted adapter. The page's first tag is uploaded as a `tag` field on its record in Algolia, Orama Cloud, and Typesense, where you can filter on it in the provider's dashboard or your own search UI. Typesense declares `tag` as a facet; on Algolia, add it to the index's `attributesForFaceting` yourself, and the sync keeps it there.
|
|
87
89
|
|
|
88
90
|
```yaml
|
|
89
91
|
search:
|
|
90
92
|
tags: [api, reference]
|
|
91
93
|
```
|
|
92
94
|
|
|
95
|
+
The search dialog doesn't read tags. Its filters group results by the sidebar section each page is in, and they show with Orama and FlexSearch, whose indexes carry each page's section.
|
|
96
|
+
|
|
93
97
|
## Ranking
|
|
94
98
|
|
|
95
99
|
Two frontmatter fields shape how a page ranks. `search.keywords` lists extra terms the page is found by, even when its text doesn't use them. It suits a page readers search for by an old name, an acronym, or a competitor's term. `search.boost` multiplies the page's relevance: above 1 moves it up, below 1 moves it down.
|
|
@@ -106,7 +110,8 @@ How much a boost moves a page depends on the adapter:
|
|
|
106
110
|
|
|
107
111
|
- **Orama** (the default) and **FlexSearch** multiply every match by its page's boost before sorting, so a boost of 3 counts exactly three times. The [MCP server](/docs/discoverability/mcp) and the [assistant](/docs/configuration/assistant)'s search rank the same way.
|
|
108
112
|
- **Pagefind** weighs a boosted page's text more heavily. It damps repeated matches, so the lift is smaller than the multiplier: a boost of 5 ranks the page higher, but closer to twice as high. Pagefind counts a boost over 10 as 10.
|
|
109
|
-
- **Algolia**
|
|
113
|
+
- **Algolia** sorts by boost among results that match about equally well, so a boost settles close calls without lifting a weaker match over a strong one. The sync adds `desc(boost)` to the index's custom ranking, after any ranking you set.
|
|
114
|
+
- **Typesense** sorts by relevance, then by boost among results with the same relevance score, so a boost settles ties without lifting a weaker match over a strong one.
|
|
110
115
|
- **Orama Cloud** fetches a wider set of results and re-sorts them by relevance times boost.
|
|
111
116
|
- **Mixedbread** searches your own store, which Blume doesn't upload to, so boosts and keywords don't reach it.
|
|
112
117
|
|
|
@@ -114,14 +119,24 @@ Keywords are searched on every adapter except Mixedbread.
|
|
|
114
119
|
|
|
115
120
|
## Search analytics
|
|
116
121
|
|
|
117
|
-
With an [analytics adapter](/docs/configuration/analytics) configured, search reports what readers look for. A query is recorded once it settles, after a second without typing or when the reader picks a result or closes the dialog, as a `search` event with the `query`, the number of `results`, and the `path` it was searched from. A query with no results shows up with `results: 0`, so filtering on it lists what your docs don't cover yet. Picking a result sends a `search_select` event with the `query`, the result's 1-based `position`,
|
|
122
|
+
With an [analytics adapter](/docs/configuration/analytics) configured, search reports what readers look for. A query is recorded once it settles, after a second without typing or when the reader picks a result or closes the dialog, as a `search` event with the `query`, the number of `results`, and the `path` it was searched from. A query with no results shows up with `results: 0`, so filtering on it lists what your docs don't cover yet. Picking a result sends a `search_select` event with the `query`, the result's 1-based `position`, its `url`, and the `path` it was picked from, for click-through.
|
|
118
123
|
|
|
119
|
-
Queries are sent as typed, up to 100 characters. Your analytics sees them the way it sees any page's search terms, so treat them as reader input
|
|
124
|
+
Queries are sent as typed, up to 100 characters. Your analytics sees them the way it sees any page's search terms, so treat them as reader input: a reader can paste an email address or a key into the box, and Google Analytics and Plausible forbid sending personal data. To keep query text away from your providers, turn it off:
|
|
125
|
+
|
|
126
|
+
```ts blume.config.ts lineNumbers
|
|
127
|
+
search: {
|
|
128
|
+
analytics: { queries: false },
|
|
129
|
+
},
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
The `search` and `search_select` events then carry `queryChars`, the query's length, in place of `query`. The text travels only on the [`blume:track`](/docs/configuration/analytics#custom-events) DOM event, as `query` in `detail.props`, so a listener you write can forward it wherever you decide it belongs, the way the [assistant](/docs/configuration/assistant#analytics) handles questions. With [cookie consent](/docs/configuration/consent) on, only readers who allowed analytics are counted.
|
|
120
133
|
|
|
121
134
|
## Adapters
|
|
122
135
|
|
|
123
136
|
The client-side adapters are keyless and need no options — their clients are configured from `i18n` (or, for Pagefind, from the built HTML), so an unknown key is rejected by config validation rather than ignored. The hosted ones take **public** credentials (safe to ship to the browser) and read their **secret** admin key from an environment variable at build time — the secret never lands in the config or the client bundle. Every option you pass to a hosted adapter is kept verbatim and handed to its search client in the browser (or, for Mixedbread, to the search endpoint), so an option Blume doesn't name still reaches the SDK — Algolia's `liteClient`, the Typesense `Client`, or `OramaClient`. Those options must be JSON values — the descriptor is inlined into the generated project as a literal, so a function, `undefined`, or a bigint fails config validation with a path instead of vanishing on the way.
|
|
124
137
|
|
|
138
|
+
Algolia, Orama Cloud, and Typesense upload your pages to their index at the end of each `blume build`. When the admin key isn't set, the build warns and skips the upload, so a build without secrets, such as a local one, still works. When the key is set and the upload fails (a wrong key, an unreachable host, a rejected record), the build fails with `BLUME_SEARCH_SYNC_FAILED` instead of deploying against an index it didn't update.
|
|
139
|
+
|
|
125
140
|
### Orama (default)
|
|
126
141
|
|
|
127
142
|
Blume's default engine. It builds a JSON index served at `/blume-search.json` and queries it in the browser — instant, client-side, and live in `blume dev` as you edit. No keys, no service. Omitting `search` entirely selects it; spell it out only when you want to be explicit:
|
|
@@ -134,7 +149,7 @@ search: orama(),
|
|
|
134
149
|
|
|
135
150
|
#### Non-Latin scripts
|
|
136
151
|
|
|
137
|
-
Orama's standard tokenizer keeps only basic Latin letters, digits and a handful of accented vowels, so text in any other script — Japanese, Chinese, Korean and Thai, but equally Russian, Greek, Hebrew and Hindi — would otherwise produce no matches at all. Blume handles this for you:
|
|
152
|
+
Orama's standard tokenizer keeps only basic Latin letters, digits and a handful of accented vowels, so text in any other script — Japanese, Chinese, Korean and Thai, but equally Russian, Greek, Hebrew and Hindi — would otherwise produce no matches at all. Blume handles this for you: pages in a locale that resolves to a non-Latin script are indexed with a word-segmenting tokenizer (built on the browser- and Node-native `Intl.Segmenter`). Declaring your site's languages is all it takes:
|
|
138
153
|
|
|
139
154
|
```ts blume.config.ts lineNumbers
|
|
140
155
|
i18n: {
|
|
@@ -143,13 +158,13 @@ i18n: {
|
|
|
143
158
|
}
|
|
144
159
|
```
|
|
145
160
|
|
|
146
|
-
The same
|
|
161
|
+
The same tokenizers serve the search dialog, the MCP server's `search_docs` tool, and assistant grounding. The script is what decides, not the language name — `az-Cyrl` is segmented while `sr-Latn` is not. With a non-Latin default, every page shares the default locale's tokenizer: Latin words survive segmentation intact, so pages in English stay searchable alongside the default language. With a Latin default, Latin-script pages keep the standard tokenizer and each translation in a non-Latin script gets its own locale's, so an English site's Japanese and Hindi pages are searchable from their own language and from a search across every language. Latin-script languages that lean heavily on diacritics (Vietnamese, or Serbian in Latin script) also fare worse on the standard tokenizer, which folds only a few accented vowels and splits words on the rest.
|
|
147
162
|
|
|
148
163
|
Japanese and Chinese go one step further. Segmenting alone indexes a compound term as its parts — 資金決済法 as 資金, 決済 and 法 — which lets a page mentioning each part somewhere outrank the page the term is actually about. Han, Hiragana and Katakana are therefore indexed as overlapping character pairs, and queries on those indexes prefer pages carrying a term's pairs together, loosening to any-pair matching when no page carries them all, so typing a whole sentence still returns its closest pages. Korean and Thai keep their segmented words.
|
|
149
164
|
|
|
150
165
|
### FlexSearch
|
|
151
166
|
|
|
152
|
-
A second keyless, client-side option. It reuses the same `/blume-search.json` index Orama ships and builds a [FlexSearch](https://github.com/nextapps-de/flexsearch) document index in the browser. Works in `blume dev` and `blume build`.
|
|
167
|
+
A second keyless, client-side option (install `flexsearch`). It reuses the same `/blume-search.json` index Orama ships and builds a [FlexSearch](https://github.com/nextapps-de/flexsearch) document index in the browser. Works in `blume dev` and `blume build`.
|
|
153
168
|
|
|
154
169
|
FlexSearch has no equivalent segmentation hook, so for sites in a non-Latin script prefer Orama (the default) or [Pagefind](#pagefind), whose `pagefind_extended` binary indexes a broad set of languages and segments Chinese, Japanese and Korean natively.
|
|
155
170
|
|
|
@@ -173,7 +188,9 @@ Pagefind only runs during `blume build`, so search isn't available in `blume dev
|
|
|
173
188
|
|
|
174
189
|
### Algolia
|
|
175
190
|
|
|
176
|
-
The browser queries [Algolia](https://www.algolia.com) directly with your search-only key. Each `blume build` replaces the index using the admin key from `ALGOLIA_ADMIN_API_KEY` (the build warns and skips the upload if it's unset). The whole index is replaced on every sync, so pages you delete or rename don't linger as stale results. The sync also adds `filterOnly(locale)` and `filterOnly(version)` to the index's `attributesForFaceting`, keeping any facets you declared yourself, because the dialog scopes results by language and docs version, and in Algolia a filter on an attribute that isn't declared for faceting matches nothing.
|
|
191
|
+
Install `algoliasearch`. The browser queries [Algolia](https://www.algolia.com) directly with your search-only key. Each `blume build` replaces the index using the admin key from `ALGOLIA_ADMIN_API_KEY` (the build warns and skips the upload if it's unset). The whole index is replaced on every sync, so pages you delete or rename don't linger as stale results. The sync also adds `filterOnly(locale)` and `filterOnly(version)` to the index's `attributesForFaceting`, keeping any facets you declared yourself, because the dialog scopes results by language and docs version, and in Algolia a filter on an attribute that isn't declared for faceting matches nothing.
|
|
192
|
+
|
|
193
|
+
Algolia caps a record at 10 KB on its Build and Grow plans, so the sync splits a longer page into several records, each carrying the page's title, description, and URL with a stretch of its text. It then sets the index's `attributeForDistinct` to `url`, unless you set your own, so a split page appears once in results, at its best-matching record.
|
|
177
194
|
|
|
178
195
|
```ts blume.config.ts lineNumbers
|
|
179
196
|
import { algolia } from "blume/search";
|
|
@@ -187,7 +204,7 @@ search: algolia({
|
|
|
187
204
|
|
|
188
205
|
### Orama Cloud
|
|
189
206
|
|
|
190
|
-
Hosted Orama. The browser queries your index endpoint with the public API key; `blume build` pushes records to the index using `ORAMA_PRIVATE_API_KEY`. Set `indexId` to enable the sync.
|
|
207
|
+
Hosted Orama (install `@oramacloud/client`). The browser queries your index endpoint with the public API key; `blume build` pushes records to the index using `ORAMA_PRIVATE_API_KEY`. Set `indexId` to enable the sync; without it, the build warns and skips the sync even with the key set.
|
|
191
208
|
|
|
192
209
|
```ts blume.config.ts lineNumbers
|
|
193
210
|
import { oramaCloud } from "blume/search";
|
|
@@ -201,7 +218,7 @@ search: oramaCloud({
|
|
|
201
218
|
|
|
202
219
|
### Typesense
|
|
203
220
|
|
|
204
|
-
Self-hosted or cloud [Typesense](https://typesense.org). The browser queries the collection with the search-only key
|
|
221
|
+
Self-hosted or cloud [Typesense](https://typesense.org). Install `typesense`. The browser queries the collection with the search-only key. Each `blume build` imports your pages into a new collection using `TYPESENSE_ADMIN_API_KEY`, then points an [alias](https://typesense.org/docs/latest/api/collection-alias.html) named after `collection` at it and drops the collection it replaced. Searches keep reading the previous collection until the new one is complete, and deleted or renamed pages don't linger as stale results. If the sync fails, the build fails and the alias keeps serving the last complete sync. The admin key needs to manage collections and aliases. On the first sync, a collection that already has the `collection` name (from an earlier Blume version) is replaced by the alias, and search keys scoped to that name keep working. Settings you hand-tune on a collection don't carry over to the next one, so reapply them after a build.
|
|
205
222
|
|
|
206
223
|
```ts blume.config.ts lineNumbers
|
|
207
224
|
import { typesense } from "blume/search";
|
|
@@ -214,9 +231,13 @@ search: typesense({
|
|
|
214
231
|
}),
|
|
215
232
|
```
|
|
216
233
|
|
|
234
|
+
Typesense's default tokenizer splits text on spaces, so in Chinese, Japanese, or Thai a query only matches from the start of a run of text. Set `locale` to your pages' language as a two-letter code (`locale: "ja"`, `"zh"`, `"th"`) and the sync tokenizes the searched fields for it; Latin-script words in those fields still match. One collection holds every language, so the setting applies to all of them.
|
|
235
|
+
|
|
217
236
|
### Mixedbread
|
|
218
237
|
|
|
219
|
-
Semantic search via [Mixedbread](https://www.mixedbread.com). Queries are proxied through a generated `/api/search` endpoint that holds your key, so this adapter **requires server output**: a host adapter such as `deployment: vercel()` from `blume/deploy` (see [Server rendering](/docs/deployment#server-rendering)). The endpoint reads `MIXEDBREAD_API_KEY`.
|
|
238
|
+
Semantic search via [Mixedbread](https://www.mixedbread.com) (install `@mixedbread/sdk`). Queries are proxied through a generated `/api/search` endpoint that holds your key, so this adapter **requires server output**: a host adapter such as `deployment: vercel()` from `blume/deploy` (see [Server rendering](/docs/deployment#server-rendering)). The endpoint reads `MIXEDBREAD_API_KEY`. Blume doesn't upload to the store: sync your content folder with the [Mixedbread CLI](https://www.npmjs.com/package/@mixedbread/cli) as part of your build, e.g. `mxbai store sync <STORE_ID> ./docs --yes` (the CLI reads its key from `MXBAI_API_KEY`).
|
|
239
|
+
|
|
240
|
+
Each result links to the page its file renders, found by the path the CLI records for the file, so run the sync from your project root or a folder above it, such as a monorepo root. A page appears once, at its best-matching passage, and passages from files that aren't pages, or from pages excluded from search, are left out.
|
|
220
241
|
|
|
221
242
|
```ts blume.config.ts lineNumbers
|
|
222
243
|
import { mixedbread } from "blume/search";
|
|
@@ -226,6 +247,8 @@ search: mixedbread({
|
|
|
226
247
|
}),
|
|
227
248
|
```
|
|
228
249
|
|
|
250
|
+
The search dialog waits for the reader to pause typing before it sends a query, and drops a query the reader has typed past. Each query sent counts toward the endpoint's [rate limit](/docs/configuration/rate-limiting).
|
|
251
|
+
|
|
229
252
|
### Disabling search
|
|
230
253
|
|
|
231
254
|
```ts blume.config.ts lineNumbers
|
|
@@ -544,7 +544,7 @@ A **Prop / Type** grid where each row expands to reveal its description and deta
|
|
|
544
544
|
|
|
545
545
|
### Auto type table
|
|
546
546
|
|
|
547
|
-
Generate a type table from a TypeScript type so the docs stay in sync with the source. Point `AutoTypeTable` at a file with `path` (resolved from your project root) and a type `name`. Descriptions come from JSDoc comments, defaults from `@default` tags, and optional properties (`?`) are marked accordingly.
|
|
547
|
+
Generate a type table from a TypeScript type so the docs stay in sync with the source. Point `AutoTypeTable` at a file with `path` (resolved from your project root, the folder you run `blume` in) and a type `name`. Descriptions come from JSDoc comments, defaults from `@default` tags, and optional properties (`?`) are marked accordingly.
|
|
548
548
|
|
|
549
549
|
```astro
|
|
550
550
|
<AutoTypeTable path="./src/button.ts" name="ButtonProps" />
|
|
@@ -685,7 +685,7 @@ The card reads the instance from [`github.host`](/docs/configuration#github-ente
|
|
|
685
685
|
|
|
686
686
|
## Component
|
|
687
687
|
|
|
688
|
-
`Component` renders an example file from your project's `examples/` directory as a live preview alongside its highlighted source, in tabs. Point it at a file with `path` — its location under `examples/`, without the extension (so `examples/counter.tsx` is `path="counter"`). React, Vue, Svelte, and Astro examples are all supported; framework examples hydrate, Astro ones render statically. It keeps the preview and the code in sync from a single file.
|
|
688
|
+
`Component` renders an example file from your project's `examples/` directory as a live preview alongside its highlighted source, in tabs. Point it at a file with `path` — its location under `examples/`, without the extension (so `examples/counter.tsx` is `path="counter"`). React, Vue, Svelte, and Astro examples are all supported; framework examples hydrate, Astro ones render statically. It keeps the preview and the code in sync from a single file. A `path` that names no example renders a "No example found" box instead, and `blume dev`, `blume build`, and [`blume doctor`](/docs/cli/doctor) warn about it (`BLUME_EXAMPLE_NOT_FOUND`) at its line.
|
|
689
689
|
|
|
690
690
|
The preview renders in an isolated frame that the docs styles never reach — no prose margins, typography, or theme chrome bleed into your component. The frame gets Tailwind (preflight + utilities scanned from your project and your examples directory), Blume's design tokens so classes like `bg-background` follow the site palette by default, and it follows the site's light/dark toggle live. The pane sizes itself to the rendered example — and keeps tracking it if the example grows or shrinks after load — with the Preview and Code tabs sharing one height so toggling them never shifts the page.
|
|
691
691
|
|
|
@@ -5,6 +5,8 @@ description: Every frontmatter field a page accepts, all optional — title, des
|
|
|
5
5
|
|
|
6
6
|
Every page accepts the following frontmatter. All fields are optional.
|
|
7
7
|
|
|
8
|
+
Frontmatter is YAML, so quote a value that holds a colon followed by a space, like `description: "Setup: the easy way"`. Unquoted, YAML reads the second colon as the start of a nested key, and Blume reports the file and line as `BLUME_FRONTMATTER_INVALID`.
|
|
9
|
+
|
|
8
10
|
<TypeTable
|
|
9
11
|
type={{
|
|
10
12
|
title: { type: "string", description: "Page title." },
|
|
@@ -163,7 +165,7 @@ seo:
|
|
|
163
165
|
creator: "@jane"
|
|
164
166
|
```
|
|
165
167
|
|
|
166
|
-
`noindex` emits a robots `noindex`, drops the page from the sitemap, and skips its structured data. `x.creator` credits the page to an X account (`twitter:creator`) — a guest post's author, say. See [Metadata](/docs/discoverability/metadata#per-page-overrides) for every field.
|
|
168
|
+
`noindex` emits a robots `noindex`, drops the page from the sitemap, and skips its structured data. A `canonical` that names another URL drops the page from the sitemap too, since the sitemap lists canonical URLs only. `x.creator` credits the page to an X account (`twitter:creator`) — a guest post's author, say. See [Metadata](/docs/discoverability/metadata#per-page-overrides) for every field.
|
|
167
169
|
|
|
168
170
|
## Search
|
|
169
171
|
|
|
@@ -175,7 +177,7 @@ search:
|
|
|
175
177
|
boost: 3
|
|
176
178
|
```
|
|
177
179
|
|
|
178
|
-
`exclude` keeps the page out of search, and `tags`
|
|
180
|
+
`exclude` keeps the page out of search, and `tags` labels its record for a hosted search adapter to filter on (see [Tags](/docs/configuration/search#tags)). `keywords` are extra terms the page is found by, beyond its own text. `boost` multiplies the page's relevance: above 1 ranks it higher, below 1 lower. See [Ranking](/docs/configuration/search#ranking).
|
|
179
181
|
|
|
180
182
|
## AI
|
|
181
183
|
|
|
@@ -184,7 +186,7 @@ ai:
|
|
|
184
186
|
exclude: true
|
|
185
187
|
```
|
|
186
188
|
|
|
187
|
-
`ai.exclude` keeps the page out of [`llms.txt` and `llms-full.txt`](/docs/discoverability/llms-txt#excluding-a-page). The page still renders, stays in search, and keeps its place in the sitemap.
|
|
189
|
+
`ai.exclude` keeps the page out of [`llms.txt` and `llms-full.txt`](/docs/discoverability/llms-txt#excluding-a-page) and out of the page map in your [site's skill](/docs/discoverability/agent-discovery#your-sites-skill). The page still renders, stays in search, the [MCP server](/docs/discoverability/mcp), and the [JSON API](/docs/discoverability/json-api), and keeps its place in the sitemap.
|
|
188
190
|
|
|
189
191
|
## Changelog
|
|
190
192
|
|
package/docs/content/i18n.mdx
CHANGED
|
@@ -140,7 +140,7 @@ Blume validates each result's structure — frontmatter, code fences, links —
|
|
|
140
140
|
|
|
141
141
|
## The language switcher
|
|
142
142
|
|
|
143
|
-
When i18n is on, a language switcher appears in the header automatically, generated from your `locales`. For each page it links the matching translation in every language; where a translation is missing it links the fallback page and marks it as not translated. There's nothing to configure. Pages from a [GitHub Releases](/docs/content/sources/github-releases) source are the exception: release notes exist in one language only, so those pages render without a switcher.
|
|
143
|
+
When i18n is on, a language switcher appears in the header automatically, generated from your `locales`. For each page it links the matching translation in every language; where a translation is missing it links the fallback page and marks it as not translated. With fallbacks off (`fallbackLocale: null`) there's no page to link, so the switcher leaves that language out. There's nothing to configure. Pages from a [GitHub Releases](/docs/content/sources/github-releases) source are the exception: release notes exist in one language only, so those pages render without a switcher.
|
|
144
144
|
|
|
145
145
|
## Routing by browser language
|
|
146
146
|
|
|
@@ -193,7 +193,7 @@ Set [`deployment.site`](/docs/deployment) so these can be emitted as absolute UR
|
|
|
193
193
|
|
|
194
194
|
## Search
|
|
195
195
|
|
|
196
|
-
Search is scoped to the active language: on a `/fr/…` page the dialog returns French results, with an **All languages** toggle to search across every locale at once. The default (Orama) and FlexSearch indexes filter in the browser
|
|
196
|
+
Search is scoped to the active language: on a `/fr/…` page the dialog returns French results, with an **All languages** toggle to search across every locale at once. The default (Orama) and FlexSearch indexes filter in the browser, Pagefind keeps an index per language, and Algolia, Orama Cloud, and Typesense carry a `locale` field on each record. Mixedbread can't scope by language, so with it every search spans every locale and the dialog has no toggle. See [Search](/docs/configuration/search) for how each adapter handles it.
|
|
197
197
|
|
|
198
198
|
## Right-to-left
|
|
199
199
|
|
|
@@ -55,7 +55,7 @@ Upgrade to the **{{plan}}** plan to use {{feature}}.
|
|
|
55
55
|
</include>
|
|
56
56
|
```
|
|
57
57
|
|
|
58
|
-
A prop applies to the included file and to anything it includes in turn, and takes precedence over a site-wide variable with the same name. Every attribute except `lang` and `meta` is a prop; attribute values are plain text.
|
|
58
|
+
A prop applies to the included file and to anything it includes in turn, and takes precedence over a site-wide variable with the same name. Every attribute except `lang` and `meta` is a prop; attribute names are lowercase, and values are quoted plain text. Props reach Markdown and MDX partials only: a `{{name}}` in an included [code file](#including-code-files) isn't replaced by a prop.
|
|
59
59
|
|
|
60
60
|
## Including code files
|
|
61
61
|
|