blume@2.1.0
Minor Changes
-
99119e7: API references can generate code samples in 18 languages: cURL, Python, JavaScript, Node.js, TypeScript, PHP, Go, Java, Ruby, PowerShell, Swift, C#, .NET, C, C++, Kotlin, Rust, and Dart. List them in
codeSamplesonopenapi()orgraphql()in the order you want them, by id or by a common alias such asgolang,c#, orts. Each sample quotes values by its own language’s string rules and updates live with the Try it form.nodeandtypescriptnow generate their own samples (axios, and typedfetch) instead of repeating the JavaScript one.Operations also render the spec’s own
x-codeSamples(orx-code-samples) as tabs ahead of the generated samples, for SDK snippets written by hand or by Speakeasy or Stainless. SetcodeSamples: falseto show only those. -
f4854ef: Add a bot check for the assistant.
ai.assistant.captchatakes an adapter fromblume/captcha,turnstile({ siteKey })orhcaptcha({ siteKey }), run invisibly: the provider’s script loads with a reader’s first question, the panel sends a fresh token with each question, and the generated route verifies it (withTURNSTILE_SECRET_KEYorHCAPTCHA_SECRET_KEY) before the model runs. A failed check answers403, which the panel shows as a translated “we couldn’t check that you’re human”; a missing secret answers with the assistant’s “not configured” notice, andblume buildwarns. With an externalendpoint, the token is sent ascaptchain the request body for that backend to verify.useAssistanttakes thecaptchasettings and averifyMessagetoo. -
253ad9e: The assistant can now search the docs and read whole pages itself. Beyond the pages retrieved up front, the model gets a
search_docstool and aread_pagetool, and can search, read, and search again before it answers, within one question and at most five steps. The tools run on your server against the assistant’s snapshot and keep to the reader’s language and docs version. When nothing matches a question up front, the assistant now searches instead of saying it doesn’t know. They’re on by default, except foropenai()with abaseUrl, whose model may not support tool calling. Setai.assistant.toolsto change either. -
6653f72: The assistant can now call OpenAI, Anthropic, Gemini, and Grok directly.
openai({ model }),anthropic({ model }),gemini({ model }), andgrok({ model })fromblume/aisend each question to the provider’s own API with your key, through its AI SDK package (@ai-sdk/openai,@ai-sdk/anthropic,@ai-sdk/google, or@ai-sdk/xai, installed as needed). They readOPENAI_API_KEY,ANTHROPIC_API_KEY,GEMINI_API_KEY, andXAI_API_KEY, turn the docs tools on, and sendreasoningas each model’s own control.openai()also takes abaseUrlfor any OpenAI-compatible endpoint, which it calls through@ai-sdk/openai-compatiblewith the docs tools off, asopenaiCompatible()did.openaiCompatible()still works and now returns the same adapter. -
aca5ac9: Add content variables. Define values under
variablesinblume.config.ts, like{ version: "2.1.0", "api-url": "https://api.example.com" }, and{{version}}anywhere in a page reads the value: in prose, headings, links, code, and component props, in.mdand.mdxpages and the files they include. Search, the.mdmirrors, andllms-full.txtshow the value too. An undefined name in prose fails the build at its line (BLUME_UNDEFINED_VARIABLE); inside code it’s left as written. Sites that define no variables are unaffected. -
9ac2262: Add cookie consent.
consentinblume.config.tstakes an adapter fromblume/consent, and with it set everyanalyticsadapter waits for the reader: its tags stay in the page as inerttext/plainuntil the reader allows analytics, then run in order, once per page load, and Vercel Web Analytics sends nothing until then.native()is Blume’s own banner, a card with Accept and Decline side by side and an optionalpolicylink, translated in every built-in language and shown inblume devtoo.osano({ customerId, configId })andethyca({ privacyCenter, propertyId, notice })load Osano or Ethyca’s Fides instead, and follow the answers readers give there. The site footer gains a Cookie settings link that reopens the banner or the manager’s preferences (any element withdata-blume-consent-openworks in a custom footer), and a reader who takes consent back gets a reload so analytics stops. The “Was this page helpful?” rating, which reports through analytics too, only shows for readers who allowed analytics. Your own scripts can wait the same way withtype="text/plain" data-blume-consent="analytics", or listen for theblume:consentevent. -
c3e1cd3: Add directory listings.
directoryin a folder’smeta.ts, or on a group in an explicitnavigation.sidebar, lists the group’s pages below the content of its own page (a folder’sindexpage, or the group’sroot):cardas a grid of cards with each page’s icon and description,accordionas rows with each subgroup a section that opens to its pages, andnone, the default, as nothing. Nested groups inherit the nearest setting, so onedirectoryin the content root’smeta.tsgives every section a listing, and any folder can set its own. The values match Mintlify’sdirectory. -
0036165: Add components for documenting endpoints by hand.
<ParamField>is a request parameter, named by where it goes (<ParamField query="limit" type="integer" default={20}>, orpath,header,body), and<ResponseField name="id" type="string">is a field of the response, with optionalpreandpostlabels. Both taketype,required,deprecated, anddefault, hold their description as content, and nest an object’s fields inside an<Expandable>. They render like the OpenAPI reference’s rows, and a page’s Markdown copy lists each field with its type and flags.<RequestExample>and<ResponseExample>hold code blocks as tabs (or a language menu withdropdown); on a wide screen they pin to a column beside the page, request above response, and stay in view while the reader scrolls, the way an OpenAPI operation’s examples do. A page withapifrontmatter (api: POST /v1/users) documents one endpoint without a spec: it shows the method and path at the top, and its<ParamField>s build a Try it panel and request samples in cURL, JavaScript, and Python, the same ones an OpenAPI operation gets, with nested fields as the properties of a JSON body.authMethodandplaygroundfrontmatter tune a page, and a new top-levelapiconfig sets theservera path joins, the defaultauth, and theplayground, whoseproxy: trueroutes sends through the built-in CORS proxy for those origins. The names and props match Mintlify’s, so migrated pages keep them as written. -
72f7cf5: Slim the header down to icon buttons, making room in it. Search is now an icon button beside the theme toggle and the assistant, in place of the search field, and each icon button names itself in a tooltip on hover and keyboard focus: “Search (⌘K)” (
Ctrl Koff Apple devices), “Toggle theme”, and “Open assistant”. The GitHub link leaves the header for the new site footer, which is now the one place the site links its repository and social profiles;navigation.repoworks as before and applies there. The header’s tabs now sit at its center on wide screens. The version and language pickers lose their border to match the icon buttons, the header’s right-hand controls sit closer together, and the language switcher drops its globe icon and always shows the language’s name. Its menu now widens to fit, so a long language name no longer runs into the “Not translated” note beside it. AHeaderoverride is unaffected. The “Open assistant” tooltip is a newassistant.openUI string, translated in every built-in language pack. -
f8bd737: Pass props to an
<include>: every attribute other thanlangandmetabecomes a value the included file reads with{{name}}, like<include plan="Pro" feature="SSO">./_snippets/upgrade.mdx</include>. Props reach the partial’s own nested includes and take precedence over a site-wide variable with the same name. -
0758eaa: OpenAPI references now document webhooks and callbacks. Each webhook in a 3.1 spec’s
webhooksgets its own page, filed under its first tag or a Webhooks group. The page shows its payload schema, the responses your endpoint should send, and an example payload in place of the Try it panel. It lists only the security the webhook itself declares, since the spec’s rootsecurityguards calls to the API, not the requests it sends. Webhook pages are in search,llms.txt, and the MCP server, and their Markdown marks them as requests the API sends. An operation’scallbacks, inline or$ref’d fromcomponents.callbacks, render in a Callbacks section on its page with their URL expression, method, request body, and responses. Specs with webhooks no longer log a warning that they’re missing from the reference. -
2153f6b: Add page layout modes.
modein a page’s frontmatter sets what it shows around its content:widedrops the table of contents and lets the content take its width,centerdrops the sidebar too and centers a wider column,customkeeps only the header for a landing page written in MDX, andframeiscustomwith the sidebar.customandframepages render no title, description, breadcrumbs, page-end links, or site footer, so they bring their own heading. Every mode stays in search and keeps its Markdown copy. The values match Mintlify’s; its full-pageassistantmode has no equivalent. -
ecbfb0e: Add narration: a “Listen to this page” player that reads each page aloud and highlights the sentence being read. Turn it on with
narration: trueto use the reader’s browser voices, which need no key and work on any host. Or passnarration: { provider: gateway({ model: "openai/tts-1-hd", voice: "alloy" }) }to generate neural audio at build: one cached clip per sentence, shipped as static files, with browser voices as the fallback inblume devor when the key is missing.The player announces callouts, steps, tabs, and collapsible sections with short spoken cues, and skips code, tables, media, and type tables. It opens a closed section or switches to a hidden tab when narration reaches it, and keeps the sentence in view until the reader scrolls away. It offers speeds from 0.8× to 2×, and stops when the reader opens another page. Set
narration: falsein a page’s frontmatter to leave it out, or adddata-blume-narration="skip"to keep any element out of narration. -
aea605b: Add pattern redirects. A redirect’s
fromcan now cover many paths, written the way Mintlify andvercel.jsonwrite them::namematches one segment (/blog/:slug),:name*as the last segment matches the rest of the path (/beta/:slug*sends/beta/a/bto/v2/a/band/betato/v2), and*ending a segment matches the rest from there (/old/article-*).toreads the captures (/v2/:slug*,/new/article-*). Every host gets them in its own syntax:_redirectssplats,vercel.jsonsources, and the routing config of a Vercel or Netlify server build, while the Node and Cloudflare servers andblume devmatch them directly. Exact redirects win over a pattern that covers the same path. A pattern that also matches a page fails the build (BLUME_REDIRECT_MATCHES_PAGE), since hosts disagree on which answers, and links into a pattern count as valid inblume validateandblume audit. -
09efac4: Add rate limiting to the server routes a reader can call: the assistant, the API playground proxy, and Mixedbread search. Each reader, by IP address, gets 30 requests per route every 10 minutes by default, and past that the route answers
429 Too Many Requestswith aRetry-Afterheader, which the assistant turns into a translated “try again in a few minutes”.rateLimitinblume.config.tstakes an adapter fromblume/ratelimit:memory(), the default, counts in the server’s memory (exact onnode(), per instance on serverless hosts);upstash()shares the count through Upstash Redis’s REST API (UPSTASH_REDIS_REST_URLandUPSTASH_REDIS_REST_TOKEN); andcloudflare()counts with Workers rate limiting, declaring the binding in the built Worker’s config. Each takesrequestsandwindow(seconds). A request whose address the host can’t tell is let through, and so is one a shared store fails to count.rateLimit: falseturns it off. -
0dd30af: Add a site footer: one row, as tall as the header, below the content on every page. Your repository link moves there from the header (see the header changes), as the first of the footer’s icons, and
footerinblume.config.tsadds the rest:links, a list of{ label, href }shown on one side, andsocials, profile URLs keyed by platform (x,discord,linkedin,youtube,website, and more) shown as brand icons on the other, both in the order written. Agithubsocial replaces the repository link, andnavigation.repostill hides it or points it elsewhere. Custom pages built onPageLayoutshow the footer too, unless they fill the layout’sfooterslot. AFooterlayout override still replaces it, andblume add footercopies the built-in into your project to edit. A site with no repository, links, or profiles has no footer.
Patch Changes
- ade0397: Add an Ask button to code blocks when the assistant is on. It opens the assistant with the block attached as a chip above the input; the question goes to the model with the code as a fenced block (up to 6,000 characters), and an empty question asks it to explain the code. The conversation shows the attached code under the question, and the button’s label and the chip’s text are translated in every built-in language. Code groups get one button in their tab strip, which asks about the tab that’s showing.
- 57db488: Add a support handoff to the assistant.
ai.assistant.supporttakes amailto:address, a URL, or a page on your site, and once there’s a conversation the panel shows a translated “Contact support” link: an email starts with the conversation as its body, and a link gets the conversation’s id as athreadquery parameter. The assistant’sask,ask_answer, andask_erroranalytics events now carry the samethread, anduseAssistantreturns it. - 0654705: Add
blume skill, which writes your docs site’s agent skill with a coding agent.blume skill --claude(or--codex) opens the agent on the newblume-write-skillskill. The agent writes aSKILL.mdgrounded in the docs that teaches an agent to use the product: setup, core concepts, common tasks, and gotchas, each linked to its page. The file goes underagents.skills, named after the site so it replaces the generated skill at/skill.md. Run it again to refresh the skill after big docs changes. - c3e1cd3: Add
wrapandexpandableto code fences.wrapafter the language (```ts wrap) wraps one block’s long lines instead of scrolling them, the waymarkdown.code.wrapdoes for every block.expandablecollapses a long block to its first lines under a fade, with a Show more toggle that opens it in full, no inner scroll, and Show less to close it again; a block under 16 lines renders as usual. Both compose with a title, line numbers, and each other, and the toggle’s labels are translated in every built-in language. - a7c8ceb: Fix header controls overlapping on narrow screens. With many controls on (tabs, a version selector, the language switcher, search, and the icon buttons), the version and language pills squeezed on top of each other on phones, and the tab bar ran into them on tablets. Below the desktop breakpoint, selectors, the version selector, and the language switcher now sit at the top of the navigation drawer, full width. The remaining header controls keep their size, and only the site title truncates to make room.
- 553a58f: Apply OpenAPI Overlays to a spec before it renders. List
overlaysbesidespecinopenapi()orscalar(), or on each source, as local paths orhttp(s)URLs, applied in order. Overlay Specification 1.0 and 1.1 are supported:update,copy, andremoveactions, with targets as RFC 9535 JSONPath. Overlays apply to the spec as written, before it’s upgraded to OpenAPI 3.1, and everything built from the spec sees the result. An overlay that fails stops its reference from rendering, the way an unreadable spec does, so it can’t publish what it was meant to hide. - d6190ee: OpenAPI overlays can now add a property named
constructor(it used to fail with a merge error), and a__proto__key in an overlay no longer reachesObject.prototype. Parsing a long run of digits in a theme color no longer takes quadratic time. - c3e1cd3: Add
pagination: falseto page frontmatter, which leaves the previous and next links off that page. The page keeps its place in the order, so its neighbors still link to it. The Mintlify migration mapshideFooterPagination: trueto it. - 7f9fa4a: Add
<View>for pages written for several audiences, such as one per programming language. Each distincttitlebecomes an option in a picker above the page’s content, and only the chosen view’s blocks show. Prose outside any<View>shows in every view, and headings in hidden views drop out of the table of contents. The reader’s pick is remembered across pages and written to the URL as?view=, a link to a heading opens its view, and the choice applies before the page paints. The agent-facing Markdown includes every view under its name. - 0a29cb2: Translate the banner, header links, and footer per locale. The banner’s
contentand linktext,navigation.actions,navigation.cta, andnavigation.featuredlabels, andfooterlink labels now accept a map of locale code to text, like tab labels do. Each page shows its locale’s entry and falls back to the default locale’s. Internal footer links now move into the reader’s locale whenever that locale serves the page, as header links already did. - 4e4b258: Add related pages.
relatedin a page’s frontmatter lists up to ten pages to suggest at its foot, shown as cards under a translated “Related pages” heading: a root-relative path shows that page’s title and description (its translation, on a translated page), a{ Title: link }entry names the card itself, and an absolute URL links off the site. Paths are checked like the page’s other links, soblume validatereports one that matches no page. The key matches Mintlify’s. - 8f040cd: Add
i18n.routeByBrowserLanguage, which sends visitors who land on the default language’s home page to the home page in their browser’s preferred language. Blume tries each language the browser prefers, in order, matching an exact locale code and then the base language, and a visitor whose first match is the default language stays. Picking a language with the switcher stops the routing for that reader, and only visitors arriving from outside the site are routed. The redirect runs in the browser before the page paints, so it works on static hosts. It’s off by default. - 169966c: Report search to your analytics. Once a query settles (a second without typing, or the reader picks a result or closes the dialog), the search dialog sends a
searchevent with thequery, its number ofresults, and thepath, so queries with no results are easy to find. Picking a result sendssearch_selectwith thequery, the result’sposition, and itsurl. Both go through every analytics adapter with an event API and asblume:trackevents. - 0f8a46e: Rank pages with
search.boostandsearch.keywordsfrontmatter.boostmultiplies a page’s search relevance: above 1 moves it up, below 1 moves it down. The default Orama search, FlexSearch, the MCP server, and the assistant apply it exactly. Pagefind weighs a boosted page’s text more heavily, Algolia and Typesense sort by it among close matches, and Orama Cloud re-sorts a wider set of results.keywordsare extra terms a page is found by, beyond its own text. Blume 1 acceptedsearch.boostwithout reading it, so a page that still sets it now ranks by it. The Mintlify migration moveskeywords,boost, andsearchable: falseinto thesearchblock. - aa2babe: Add
seo.metatagsfor tags written into every page’s head: site-verification tokens,theme-color, an app banner, and anything else Blume has no setting for. Open Graph families render withpropertyand everything else withname. A tag Blume writes itself, such asdescription,robots,og:image, or atwitter:card tag, is refused, and the build names the setting that controls it. - 7dee91b: Publish an agent skill for every site. The build writes a
SKILL.mdnamed after the site’s title, served at/skill.mdand listed in the skills discovery index,llms.txt, and the AI catalog. It’s built from what Blume already knows, with no model call: how to read any page as Markdown,llms.txt, the MCP server, the API references, the changelog, and a map of the docs with each page’s description. It needsdeployment.sitefor its absolute links. A skill of the same name inagents.skillsreplaces it, andagents.skillMd: falseturns it off. - 7a58b53: Meet WCAG AA contrast with the accent colors, and check a site’s theme colors in
blume audit. Each accent preset now has a darker light-mode shade and a lighter dark-mode shade, so accent text and button labels reach 4.5:1 in both modes. Blue is the default, so this changes sites that set no accent too. Labels on accent and action fills are white while white reaches AA on that color, and dark otherwise. A light accent, and every preset in dark mode, now gets dark labels instead of unreadable white ones. A newaccessibilitycategory inblume auditmeasures a customtheme.accent,theme.action, ortheme.backgroundagainst the same bar in both modes, and says when it can’t read a color. - 0c0acd5: Tell the translation agent to keep frontmatter valid YAML by quoting a translated value that contains a colon or starts with a quote or backtick, and name the YAML error and its line and column when
blume translaterejects a page whose frontmatter doesn’t parse. - f50ef8a: The
BLUME_WIKILINK_UNRESOLVEDandBLUME_WIKILINK_AMBIGUOUSdiagnostics now link to the Obsidian source page instead of the content sources overview. - aecd4b9: Add written feedback.
feedback: { comments: true }offers a box after the “Was this page helpful?” rating where the reader can say what worked or what’s missing. A comment is sent as afeedback_commentevent, with the rating, path, title, and the comment (up to 1,000 characters), through every analytics adapter with an event API and as ablume:trackevent. The box’s label and button are UI strings, translated in every built-in language.feedback: trueandfalsework as before, and theFeedbacklayout slot now receives acommentsprop.