Changelog
August 3, 2026
Patch Changes
- 4d7dc87: Negotiate
Accept: text/markdownon the homepage even when it’s a landing page. A user-authored home page has no Markdown source, so agent requests for a markdown homepage previously fell through to HTML; the homepage’s mirror now falls back to thellms.txtindex — the machine-readable map of the site — served at/index.mdand wired into the dev server, the Vercel routing config, and the homepageLinkheader’srel="alternate"entry. - ff31ab5: Remove a polynomial-backtracking regex from the font-name slugifier’s dash trim
- 02eb6c7: Fix the homepage
Linkheader andVary: Acceptnever being sent on Vercel deploys. The injected header routes sat afterhandle: "filesystem"in the Build Output config — the miss phase, which prerendered static responses never reach — so agent-readiness checkers saw noLinkheader onGET /. Both header routes now ride in the main phase, ahead of static-file matching. - de62812: Stamp an
x-markdown-tokensheader (estimated token count, ~4 characters per token) on Markdown responses, following the Cloudflare Markdown for Agents convention: the raw-Markdown endpoints send it on dev and server-rendered responses, and the Vercel routing config carries it on the negotiated homepage.
August 3, 2026
Minor Changes
- a089dbc: Upgrade the AI SDK to v7. The generated Ask AI endpoint now passes
instructionsinstead of the deprecatedsystemoption, and the optional provider peer dependencies moved to their AI SDK 7-compatible majors:@openrouter/ai-sdk-provider@^3and@ai-sdk/openai-compatible@^3. If your Ask AI backend uses one of those providers, upgrade the provider package when you update Blume; gateway-backed setups need no changes. - 56979c0: Built-in image optimization for local images. A relative image reference (
) next to your content is now optimized at build time end-to-end — compressed, converted to WebP, and stamped with intrinsicwidth/heightso the layout doesn’t shift while it loads. sharp now ships as a Blume dependency, so the Astro image service works out of the box under every installer (it was previously unresolvable from the generated runtime under isolated linkers, failing the build with “Could not find Sharp”). Agent-facing surfaces (/<route>.md,/<route>.mdx, llms-full.txt, MCP) rewrite relative references to a new/blume-assets/content/…endpoint serving the original files, so image links in raw Markdown resolve instead of 404ing; the same endpoint serves remote-source assets materialized under.blume/public/blume-assets, which builds previously never shipped. A newimageconfig (domains,remotePatterns) authorizes remote hosts for optimization, mapping directly onto Astro’simageconfig. - bd2b8cb: Custom fonts:
theme.fontsroles now accept any Google/Fontsource/Bunny/Fontshare family by name ({ name, provider?, weights?, fallback? }) and local font files ({ name, variants: [{ src, weight?, style? }] }) alongside the curated slugs — all self-hosted and optimized through Astro’s Fonts API.seo.og.fontsgains a matching local form ({ name, src, weight?, style? }), and when a config setstheme.fontsexplicitly, the generated Open Graph cards now render in the theme’s display and body fonts automatically (explicitog.fontsstill wins;og.fonts: []opts out). Sites that never touchedtheme.fontsare unaffected. - 6e293cf: Publish a Web Bot Auth signature directory from
ai.webBotAuth.keys. The configured public JWKs are served at/.well-known/http-message-signatures-directorywith the registered media type on every build surface (static hosts via_headers, Vercel server builds via a Build Output content-type override), advertised inagent-readability.json, and validated to be public-key-only — a JWK containing private material is rejected at config time. - 64bac00: Publish Agent Skills for discovery from
ai.skills. Point the new config field at a directory of skills (each subdirectory holding aSKILL.md) and the build publishes them per the Agent Skills Discovery RFC v0.2.0: single-file skills verbatim at/.well-known/agent-skills/<name>/SKILL.md, skills with supporting resources as deterministic.tar.gzarchives (execute bits preserved), and a discovery index at/.well-known/agent-skills/index.jsonwith the v0.2.0$schemaand per-skill SHA-256 digests. Artifacts get explicit media types on static hosts, the index is advertised inagent-readability.json, and spec-invalid skills are skipped with a build warning. - 4e7a824: Generate an RFC 9727 API catalog at
/.well-known/api-catalog. The linkset is derived from the site’s configured APIs — each OpenAPI/AsyncAPI reference (anchored at its docs route, withservice-docand, for remote specs,service-descrelations) and the hosted MCP server (with its discovery document as the service description). Served asapplication/linkset+jsonon every build surface, advertised via arel="api-catalog"homepage Link header and inagent-readability.json. Sites with no APIs emit no catalog. - f4e6ce7: Serve Markdown to agents through
Accept: text/markdowncontent negotiation on Vercel server builds. The build splices header-conditional rewrite rules into the Vercel routing config, so a content-page request that preferstext/markdowngets the page’s raw-Markdown mirror at the same URL — withVary: Accepton both variants — andagent-readability.jsonnow advertisescontentNegotiationonly on deployments that honor the header. - 7d426b2: Add
seo.og.siteandseo.og.descriptionoverrides for the generated OG card’s footer URL and subtitle, and acceptfalseon them (and onseo.og.logo) to hide that layer of the card entirely - a730bef: Check DNS-AID agent discovery in
blume audit. Whendeployment.siteis set, the network tier (--url) queries_index._agents.<host>for ServiceMode SVCB/HTTPS records over DNS-over-HTTPS and reports the exact record to publish when none exist, plus whether the answers are DNSSEC-authenticated. SetBLUME_DOH_URLto use your own resolver. - cacab69: Register WebMCP tools on every page. Agentic browsers with a model context (
navigator.modelContextordocument.modelContext,provideContextorregisterTool) get the docs’ read-only surface as in-page tools:search_docs(lazy-loads the configured search client on first call),get_page(a page’s raw-Markdown mirror), andlist_pages(the llms.txt index). The script is tiny and no-ops in browsers without the API. On by default; setai.webmcp: falseto opt out. - 8567927: Advertise the agent-facing surface with an RFC 8288
Linkheader on the homepage. The header points agents atagent-readability.jsonandllms.txt(rel="describedby") and the homepage’s raw-Markdown mirror (rel="alternate"; type="text/markdown"), and is emitted on every surface Blume controls: the dev server, the_headersfile on static builds (Netlify/Cloudflare), and the routing config on Vercel server builds. - 8c46244: Align the MCP server card with the SEP-2127 Server Card extension schema.
/.well-known/mcp/server-card.jsonnow declares the published$schema, a reverse-DNSnamederived from the site host,title,websiteUrl, andremotestransport endpoints (absolute, so present once the site URL is known), alongside initialize-shaped compat fields (serverInfo,capabilities,transports) for scanners built against the proposal’s earlier revision. The advertised tool set and existingtransport/urlfields are unchanged.
Patch Changes
-
a92b52f: Align the table of contents, search results, search preview, and Ask AI scrollbars with the sidebar’s thin scrollbar styling
-
1794284: Update katex to ^0.18.1
-
a4f4a62: Update tailwindcss to ^4.3.3
-
4202f80: Rank Japanese and Chinese search results by whole compound terms rather than their parts. Dictionary segmentation cuts a term like 資金決済法 into 資金 / 決済 / 法, and because Orama scores a bag of words, a page mentioning each fragment somewhere could outrank the page the term is about — on one 65-page Japanese site every law-name query returned its index page first. Han, Hiragana and Katakana runs are now indexed as overlapping character bigrams, and queries on those indexes look for documents carrying all of a term’s bigrams before falling back to the any-token default, so sentence-like queries still return their closest pages. Korean and Thai keep their segmented words, and Latin terms are still indexed whole — though on a Japanese or Chinese index a query of several Latin words now also prefers pages carrying all of them, with the same any-token fallback.
-
04d37f1: Give the code-block copy button a visible surface and cap code block height
The copy button previously rendered transparent over the code, making it hard to see against syntax-highlighted lines. It is now an opaque chip with hover states and a check icon that swaps in after copying. Code blocks taller than 24rem now scroll vertically in place (on the inner code scroller, so the header bar and copy button stay put), with thin theme-colored scrollbars matching the sidebar treatment and a brighter thumb in dark mode. The scroller is keyboard-focusable (the tab stop moves from the pre to the element that actually scrolls), print output renders capped blocks in full, and the Component source pane keeps its own measured height. Copy success is now announced to screen readers via a polite live region, using the existing localized “Copied!” string.
-
76ce58f: Let Mermaid diagrams take the full content width instead of shrink-wrapping to the SVG’s 300px fallback size; small diagrams stay centered and diagrams that set
useMaxWidth: falsestill scroll horizontally -
cbec130: Include
deployment.basein the generated OG card’s footer site text, so a subpath deploy (like a GitHub Pages project site) showsuser.github.io/repoinstead of the bare shared host -
87a3e9a: Drop empty table header rows. GFM requires a header row, so a table that doesn’t want one is authored with blank header cells (
| | |) — that used to render as a dead band above the body; the empty<thead>is now removed. A header cell containing any non-text content (an image, an icon) still counts as non-empty. -
5951ee0: Support @sanity/client v7. The optional peer dependency range is now
^6.21.0 || ^7.0.0, andblume initscaffolds new Sanity projects with v7. -
f3334cd: Harden two code-scanning findings: the WebMCP search tool now strips search-hit markup so no
<fragment (such as a dangling<script) can survive mangled highlighting, and the API catalog trims the configured site origin with the lineartrimEndhelper instead of a quadratic trailing-slash regex. -
67bdef6: Define the
<skill>placeholder in the blume-migrate skill so the codemod and oxfmt-patch commands resolve to the skill’s own directory instead of failing on a literal<skill>path -
1500d32: Resolve the bundled docs path from the installed
blumepackage instead of a barenode_modules/blume/docs. Theblumeandblume-migrateskills now tell agents to locate the package from the workspace that depends on it (viarequire.resolve('blume/package.json')), so the docs lookup works in pnpm workspace monorepos where the package is not installed at the repository root. -
ea3c5fd:
blume audit --verbosenow prints each finding’s full message under the affected page, so link checks name the broken target instead of only the page containing it. -
3afcf7a: Move Blume’s own
zoddependency from v3 to v4 (^4.3.6), the major Astro 7, Scalar, and the MCP SDK already use, so an install resolves one Zod major instead of a v3 copy hoisted beside nested v4 copies. That mixed tree is what made partially updatednode_modules(a restored CI cache that a dependency bump left half-reconciled) resolve Scalar’s schemas against a Zod withoutz.function().optional, failing builds withTypeError: z.function(...).optional is not a function. Resolved configs and frontmatter parse exactly as before: shorthand defaults that Zod 4’s.default()would return unparsed — collapsing blocks likethemeorseoto a bare{}instead of their fully-defaulted shape — now use.prefault(), which keeps Zod 3’s parse-the-default semantics.frontmatter.extendschemas still go through the Standard Schema contract, so any Zod version (or Valibot, or ArkType) works there unchanged.
July 30, 2026
Patch Changes
- a015b0a: Match CJK and Thai content in the default Orama search provider. With
i18n.defaultLocaleset to a language written without spaces (Japanese, Chinese, Korean, Thai), the search index now uses a word-segmenting tokenizer built onIntl.Segmenter— previously every query in those scripts silently returned zero results because the default tokenizer collapsed the text to no tokens. The fix covers the search dialog, the MCP server’ssearch_docstool, and Ask AI grounding, and keeps Latin terms matching case-insensitively on mixed-language sites. - 454e67f: Match every hoverable sidebar navigation row — the back rows, routed panel header, and flat group header — to the shared 0.65rem navigation radius.
- 17d520b: Style the desktop sidebar scrollbar with a thin, theme-colored thumb and a transparent track. The mobile drawer and the page scrollbar keep their platform defaults. Requires Tailwind CSS 4.3 or later, which is now the declared minimum.
July 27, 2026
Minor Changes
- 217975c: Add a top-level
integrationsarray toblume.config.tsfor registering Astro integrations. Entries are schema-validated as an array (each element is left for Astro to validate) and appended after Blume’s built-in integrations in declaration order, with no sorting or deduplication. The generated Astro config loads them through a portable bridge back toblume.config.tsrather than serializing the instances, so function-bearing hooks survive across build,blume dev, config regeneration, and eject. Install and version each integration in the site itself — Blume neither adds it to the runtime’s dependencies nor manages its Astro compatibility. - 592af35: Add
blume eval, a test suite for your docs. An AI agent — Claude Code by default, Codex with--agent codex, spawned from your own installation with no API keys held by Blume — answers the questions inevals.yamlusing ONLY the documentation, served over a private MCP stdio bridge to an agent locked out of its file, shell, and web tools; a judge pass then grades each answer against the expected facts you listed. Any question the docs can’t answer fails CI (relax with--threshold), each failure is anchored to the source page that should answer it,--jsonemits the validate/audit-compatible machine report,--fixhands the failing report to the agent to edit the docs interactively, andblume eval initdrafts a starter evals file from your existing docs. No build or deployment is needed — the docs snapshot is computed from the content tree. - 2063196: Let a header tab declare its link target with
href. A tab’spathscopes its sidebar section and doubles as the link, so a section whosepathisn’t a page of its own falls back to the section’s first page rather than linking to a 404. That fallback only sees the content tree, so a tab pointing at a route generated outside it — the automatic/changelogindex, or a custom page underpages/— lands on the section’s first entry instead of the page the reader expected. Settinghrefkeeps the tab on the declared route; the field is optional and tabs that omit it resolve exactly as before. Declared hrefs are localized and rebased like any other route, so they work under i18n and adeployment.base. - 55e176a: Support external Ask AI endpoints on static sites and per-source OpenAPI search, llms.txt, and crawler indexing controls.
Patch Changes
- 18e1d8d: Stop Cloudflare server builds from declaring unused
SESSIONKV andIMAGESbindings in the generated wrangler config. Without a configured session driver,@astrojs/cloudflareforce-enables KV-backed sessions — makingwrangler deploydemand a real KV namespace nothing reads — and defaults images to the runtime Cloudflare Images binding. Blume never readsAstro.sessionand every HTML route prerenders, so the generated Astro config now sets an inert in-memory session driver andimageService: "compile", which pre-optimizes images at build time with sharp. - 38475cb:
blume initnow addsnode_modules/to the generated.gitignorealongside Blume’s runtime and build output directories. - 0c8f78d: Make the entire sidebar navigation back row clickable. When a drilled-in section has no index page, the chevron and title now form a single full-width back button; when it does have one, the title link and back button each fill their side of the row so there are no dead zones, and both get row hover states.
- 812b09e: Fix nested
<Tree>folder chevrons, nested<Accordion>chevrons, and a nested object schema’s “Show properties” toggle reflecting an ancestor’s open state instead of their own. All three rotated or flipped on Tailwind’sgroup-open:variant, which matches any open ancestor.group— the same leak as the nested sidebar chevron — so a collapsed disclosure inside an expanded one showed an open indicator. Each indicator is now scoped to its owndetails. - 812b09e: Fix a nested sidebar group’s chevron pointing down while the group is collapsed. The chevron rotated on Tailwind’s
group-open:variant, which matches any descendant of an open.group— and since every collapsible group in the tree is a.group, expanding a parent rotated the chevrons of its collapsed children too, so the arrow disagreed with the items it was hiding. The rotation is now scoped to the group’s owndetails, leaving each chevron to reflect only its own open state. - d5d6b7a: Fix the EPUB page action failing in dev with
epub is not a function.epub-gen-memory’s browser bundle is a browserified UMD, and its dynamic import lives insidenode_modules/blume, which Vite’s optimizer scan doesn’t crawl — so in dev it was served as raw ESM, where the UMD finds noexports/define, exposes nodefault, and strands its callable onwindow.epubGen. It now joins mermaid inoptimizeDeps.include, naming the/bundlesubpath that is actually imported, since optimizing the package root leaves that entry unoptimized. Production builds already bundled it correctly and are unchanged. - 55e176a: Pagefind now honors search exclusions: pages with
search.excludefrontmatter (and hidden pages, unlesssearch.indexing.includeHiddenPagesopts them in) no longer appear in local search results.
July 23, 2026
Patch Changes
- e4506a0: Keep the Ask AI panel open when Escape dismisses the search dialog stacked on top of it. The panel’s window-level Escape listener fired alongside the dialog’s native cancel, closing both surfaces when the user only meant to close search.
- e4506a0: Stop reporting every
blume audit --claude/--codexlaunch failure as “not found on PATH”. Only a missing executable (ENOENT) gets the install hint now; any other spawn failure (EACCES,EMFILE, …) surfaces as itself instead of being masked by an irrelevant install suggestion. - e4506a0: Strip
deployment.basebefore comparing canonical and sitemap URLs inblume audit. Canonicals and<loc>s are emitted assite + base + routewhile page URLs come from the base-less file tree, so on a subpath deployment every page false-firedCANONICAL_BAD_TARGET,NON_CANONICAL_IN_SITEMAP, andINDEXABLE_PAGE_NOT_IN_SITEMAP— and the duplicate-content checks silently skipped every page (each one looked like it canonicalized elsewhere). - e4506a0: Percent-decode pathnames before comparing them against the built file tree in
blume audit. Sitemap<loc>s areencodeURI’d andURL#pathnamere-encodes non-ASCII, while page URLs and file-index keys are raw on-disk names — so a non-ASCII route (e.g. a Japanese slug) false-firedSITEMAP_BAD_URL, and a percent-encoded href false-firedLINK_TO_BROKEN. - e4506a0: Probe live URLs under
deployment.baseinblume audit --url. Page URLs come from the base-less build tree, but the deployed site serves everything (pages, robots.txt, sitemap.xml) under the base — so auditing a healthy subpath deployment produced a wall ofHTTP_4XXfindings from probing the wrong URLs. - e4506a0: Strip query strings and fragments from redirect destinations before
blume auditchecks them against the build. A working redirect to/guide#setupor/search?q=xwas reportedREDIRECT_BROKENbecause the suffixed path is not a file-tree member. - e4506a0: Apply
basePathto configured redirects beforeblume auditresolves them. Redirects are authored as if mounted at root and gain the base at build time, but the audit compared them raw against built page URLs that carry the base — so every redirect on abasePathsite was reportedREDIRECT_BROKEN, whileLINK_TO_REDIRECTandREDIRECT_SOURCE_IS_PAGEcould never fire. - e4506a0: Keep
/separators in Sanity and Notion slugs. Slugging deleted slashes along with other punctuation, so aguides/setupslug was mashed intoguidessetup— and two documents whose slugs differ only by a slash silently overwrote each other. Segments are now slugged individually. - e4506a0: Stop promoting a
title="…"embedded in another code-fence meta attribute’s quoted value (caption='set title="X" here' file.ts) to the block title. Other quoted attributes are blanked before the explicit-title scan, so the bare-token title (file.ts) wins as intended. - 4294b00: Carry the resolved
dateFormatconfig into the runtime data and itsBlumeDataConfigtype. A configureddateFormatwas silently dropped from the serialized site data, so the date stamps always rendered the default long style, andblume checkfailed with ts(2339) ondata.config.dateFormatin the generated catch-all page. - e4506a0: Fall through to the next platform env var when one is set but empty.
VERCEL_PROJECT_PRODUCTION_URL=""dead-ended the chain beforeVERCEL_URL(same for Netlify’sURL/DEPLOY_PRIME_URL/DEPLOY_URL), leavingdeployment.siteunset so canonicals, OG images, and the sitemap silently switched off for that deploy. - e4506a0: Leave the
.blume/node_modulesjunction alone when it already points at the right target. It was deleted and re-created on every dev regeneration in the split-install layout, opening a window in which the dev server’s module resolution raced a missingnode_modulesand intermittently failed with “Cannot find package”. - e4506a0: Watch
blume.config.ts,theme.css, andcomponents.tsvia their parent directory inblume dev. Watching the file path tracks the inode, so a rename-replace save (vim and most “atomic save” editors) orphaned the watcher after the first save — every later edit was silently ignored until the server restarted. - e4506a0: Localize internal
navigation.featuredhrefs per locale, like header tabs. A pinned/changeloglink rendered on/fr/…pages always targeted the default-locale route, kicking the reader out of their language. - e4506a0: Stop opening a phantom code fence on a line-leading inline backtick span. A paragraph line like
```inline```is not a fence opener (CommonMark forbids backticks in a backtick fence’s info string), but the heading/link scanner treated it as one and silently dropped every heading and link after it from the TOC, search index, and anchor validation. - e4506a0: Stop misreading a body-leading thematic break as front matter in heading extraction. A stripped body opening with
---followed by a blank line lost every heading up to the next---line — missing TOC and search entries, and falseBLUME_BROKEN_ANCHORfindings fromblume validate. - e4506a0: Strip the locale directory from a shared
.$file’s nav path with thedirparser.fr/changelog.$.mdxkept itsfr/segment, silently routing the default locale’s record inside the French URL namespace, the French copy to/fr/fr/changelog, and conjuring a spurious “Fr” sidebar group. - e4506a0: Skip images that are themselves links when wiring click-to-zoom. A linked image (
[](https://example.com)) navigates on click, so the zoom binding only flashed an overlay in the instant before navigation while thecursor-zoom-inaffordance promised a zoom that never happened. - e4506a0: Extract markdown link targets with balanced parentheses and image-wrapped labels intact.
[wiki](https://en.wikipedia.org/wiki/Foo_(bar))was truncated at the inner)and reported as a broken link, and the outer target of[](/target)was never validated at all (the nested image’s own target still is). - e4506a0: Make the MCP tools’ contract hold together:
search_docshits now include theroutethe tool description promises (alongsideurl), andget_pageaccepts a full URL or a base-prefixed path — an agent following “pass a route fromsearch_docs” no longer gets “No page found” for a page that exists on a site withdeployment.siteordeployment.baseconfigured. - ff0b2b0: Fix
.mdpages serving stale content inblume dev. The generated dev config kept Vite’s watcher out of Astro’s cache dir, which suppressed thedata-store.jsonchange events Astro relies on to invalidate content in a running dev server — so edited Markdown bodies (rendered into the data store at load time) kept serving the old HTML even after a hard reload, while.mdxpages (rendered through their own module) updated fine. The watcher ignore is now scoped to migrated (content.root: ".") projects, the only layout whose glob loader would otherwise churn on Astro’s own cache writes. - e4506a0: Pair backtick code in OpenAPI descriptions the way CommonMark does — a run only closes on an equal-length run. A lone inline backtick followed by a code fence used to “close” on the fence’s first backtick, leaving
{/<in the surrounding prose unescaped (an MDX compile error that fails the operation page’s build) and entity-escaping the fence body. - e4506a0: Stop escaping
>in OpenAPI descriptions rendered to MDX. It isn’t MDX-special on its own, and escaping it turned a common> **Note:** …blockquote into a literal “> Note:” paragraph. - e4506a0: Give every distinct OpenAPI tag a unique slug. Slugging strips all non-ASCII, so two non-Latin tags (
ペット,注文) both collapsed tooperations— merging their routes and sidebar groups and dropping the second tag’s overview section. Colliding slugs now gain-2,-3, … in first-seen order. - e4506a0: Leave shifted and alted keyboard chords to the browser. Ctrl+Shift+I (DevTools) toggled the Ask AI panel and Ctrl+Shift+K (Firefox web console) opened the search dialog, because the ⌘I/⌘K matchers ignored the Shift and Alt modifiers.
- e4506a0: Resolve explicit sidebar refs written with a trailing slash. A hand-written
"guides/"normalized to/guides/, missed the slashless/guidesroute, and the item was silently dropped from the sidebar with no diagnostic. - 6554485: Keep the docs sidebar scrolled to the current page across navigations. Each page load previously reset the sidebar’s own scroll container to the top, so on long sidebars the viewport visibly jumped away from the link you just clicked. A pre-paint inline script now centers the active link when it would otherwise be out of view — this also fixes deep links landing with the current page’s link below the fold. Short sidebars, and pages whose active link is already visible, are untouched.
- e4506a0: Only unwrap a single rendered paragraph in
<Prompt>,<Frame>, and<Tooltip>. The greedy unwrap matched across multiple paragraphs, injecting unbalanced</p>/<p>tags viaset:html— a multi-paragraph description, caption, or tooltip label broke the surrounding layout when the parser re-parented the stray tags. - e4506a0: Dedupe repeated
<Update>ids on a page, mirroring the accordion id dedupe. Two entries labeled “Bug fixes” produced duplicate DOM ids, so the second entry’s self-anchor permalink jumped to the first one; later duplicates now gain-2,-3, … and their header anchors follow. - e4506a0: Make
useAskAI().reset()revoke the in-flight stream, matching the built-in island. Resetting mid-answer used to let the next chunk re-append an orphaned assistant bubble onto the emptied conversation, and a fetch error after reset resurrected the entire pre-reset history. The request is now aborted and stale writes are discarded.
July 22, 2026
Patch Changes
- 42522fc: Downlevel
<Component>to its example’s source in agent-facing Markdown. The/<route>.mdmirror,llms-full.txt, and the MCPget_pagetool now render<Component path="…" />as a fenced code block of the example’s source (the same code the on-page “Code” tab shows) instead of leaving the raw JSX tag, so agents reading a page get the component’s code rather than an opaque element. An unknown path (or a missingpath) is left verbatim, and a same-nameai.markdownComponentsserializer still overrides the built-in. - a71f70e: Add a
dateFormatconfig option for the “last updated” stamp and the changelog timeline. Both surfaces previously hardcodeddateStyle: "long"; they now share a configurable pass-through toIntl.DateTimeFormatoptions, defaulting to{ dateStyle: "long" }so existing sites are unchanged. Set a preset (dateFormat: { dateStyle: "medium" }) or a numeric house style (dateFormat: { year: "numeric", month: "2-digit", day: "2-digit" }); dates still render in the site’s locale and in UTC unless atimeZoneis given. - 68fc939: Harden the agent-facing code-fence helper against a polynomial-time regex (ReDoS). Trailing newlines are now stripped with an unambiguous pattern, so example source with many interior blank lines can’t force quadratic backtracking.
- ee77cfd: Stop long OpenAPI routes from overflowing the native API reference layout. An operation’s heading now wraps a long
METHOD /pathtitle instead of clipping it off the content column, and the overview list rows stack the summary over the route (each getting the full row width) and wrap a long route inside the card — dropping the duplicate path that overlapped the label when a spec sets no summary. - 93a94a2: Fix two responsive/mobile layout issues. Twoslash code blocks now wrap their lines on narrow screens instead of pushing the page sideways (hover popups still escape as before), and a long site title in the header now truncates on one line instead of wrapping into the fixed-height bar.
- a27c543: Add a
scalarpassthrough object to theopenapiandasyncapiconfig blocks (Scalar renderer). Any Scalar configuration set there is forwarded verbatim to the embedded<ScalarComponent>—localization(to translate Scalar’s own UI),agent,hideTestRequestButton,orderSchemaPropertiesBy, and the rest. Options in thescalarobject win over Blume’s derived spec/theme config, making it a full escape hatch to Scalar’s API; the dedicatedthemefield remains the ergonomic shorthand. - fa07dc4: Create the
.blume/node_modulesdependency junction when an isolated linker (Bun’sisolatedmode, pnpm) dedupes the workspace’s ownastrodependency to Blume’s copy. The walk from.blume/found the “correct” astro through the workspace’s direct-dep symlink — in a directory holding none of Blume’s integrations — so the junction was skipped and the build died onCannot find module '@astrojs/mdx'.
July 20, 2026
Patch Changes
- 6cf2995: Allow custom Shiki theme objects in
markdown.codeBlocks.theme.lightandmarkdown.codeBlocks.theme.dark. Custom themes now flow through fenced code, inline highlighted code,<CodeBlock>, and<Diff>alongside bundled Shiki theme names. - cb30a40: Recognize the localized/based root tab when scoping the sidebar. With i18n enabled and header tabs configured, a non-default locale’s tabs arrive localized (
/becomes/en), but render-time scoping compared the active tab against a bare/— so on any/en/...route the root tab was misread as a section tab, and a root-level(group)folder (whose path is exactly the locale prefix) collapsed the sidebar to that one group. The same bare comparison blanked the sidebar entirely under abasePathwith a root tab. The navigation now carries its root in the tabs’ own path space (/,/en,/docs) and the sidebar scoping compares against it, so non-default locales show the full tree minus tab-owned sections, matching the default locale. - aa3f588: Size
<Component>preview panes to the rendered example instead of the source line count. The generated frame page now observes the example with a ResizeObserver and reports its height to the docs page, which applies it to both the Preview and Code tabs — so short sources that render tall UIs no longer clip, and long sources that render small components no longer float in dead space. The line-count estimate remains the SSR/no-JS initial height (288px floor, 400px ceiling; the measured height is unceilinged up to the viewport), with a height transition so the settle on lazy load doesn’t snap. Examples that resize after load keep the pane in sync, and viewport-tracking examples (h-screen) can’t feed the measurement back into unbounded growth. - 103733d: Re-point a cache-restored
.blume/node_modulesjunction that resolves a superseded Blume install. A restored build cache (e.g. Vercel’s) could resurrect the junction pointing into the previous release’s store directory; because releases rarely bump Astro, every astro-based health check passed straight through the stale link, and the freshly generated Astro config then imported the old package — crashing on any export added since (blumeTwoslashTransformer is not a function). The junction is now dropped and relinked whenever the directory behind it holds ablumeother than the one running.
July 19, 2026
Patch Changes
- b5ed87a: Add
seo.og.titlesto name the generated Open Graph card of a custom.astropage, keyed by route. A custom page has no frontmatter to read, so its card was titled by humanizing the last URL segment — turning/cliinto “Cli” with no way to say “CLI”. An entry here wins over the humanized segment;"/"addresses the home, whose card otherwise carries the site title. - 52cfa77: Give each GitHub-sourced changelog release page a unique meta description derived from its release notes, instead of every release falling back to the site-wide description. The summary is the notes reduced to plain text — section headings (“### Patch Changes”) and changesets’ commit-hash bullet prefixes dropped, code fences and link syntax stripped — then cut at a word boundary to fit the 110–160 character search-snippet range
blume auditchecks for. It’s carried asseo.description, so it feeds the meta/OG/Twitter description tags without adding a visible lede paragraph to the page. - 20ae16d: Fix
blume checkfailing on generated files under a strict tsconfig: island and example wrappers now mirror the wrapped component’s props ontoAstro.propsso required props type-check through the spread, and the OG endpoint’scustomRoutesarray is explicitly typed so an empty list is no longer an implicitany[]. - 9a6345f: Decouple Twoslash from the project’s hoisted TypeScript so sites can use TypeScript 7 (tsgo). The generated Astro config now wires in
blumeTwoslashTransformerfromblume/markdown, which compiles Twoslash fences with Blume’s own pinned classic TypeScript (passed explicitly astsModuleplustsLibDirectoryfor the default lib files) instead of resolving whatevertypescriptthe surrounding project installed — under TS7 that package’s main export is a version stub with no compiler API and nolib.*.d.tsfiles, so anytwoslashfence crashed the build. - 32ced54: Evaluate
{frontmatter.*}prop expressions when downleveling components for agent-facing output. Serializers — built-in andai.markdownComponents— now receive the same values the rendered page shows instead of empty props, and the page’s front-matter is exposed on the serializer context. - ff37999: Stop
blume auditflagging the changelog RSS feed link in llms.txt as a stale entry. The stale-entry check compared each llms.txt target against built pages only, but the generator itself links non-page assets — the changelog RSS feed — so a target the static file index serves now counts as valid, the same way redirect targets may land on a served asset. - f322ac1: Warn when an index page’s own frontmatter
titlediverges from its folder’s explicitmeta.title. The two are resolved independently, so a translator can update one and forget the other — the sidebar looks right while the page’s own<title>/heading stays stale. Reported asBLUME_NAV_INDEX_TITLE_MISMATCHfromblume doctor/blume check. A mismatch is reported once — untranslated pages filled in from the fallback locale are exempt, since their fix is translating the page, not editing the fallback locale’s frontmatter. - eb25103: Link Blume’s nested integrations into the generated runtime when npm’s split install hoists astro away from them. An
overridesastro pin plus an incrementalnpm install— the exact steps the Astro-conflict warning recommends — hoists astro to the project root while@astrojs/mdxand Blume’s other deps stay nested undernode_modules/blume/node_modules, so fresh checkouts andblume build --isolatedfailed withCannot find module '@astrojs/mdx'. The dependency link now probes for the integrations instead of astro alone and links the nested set, letting astro keep resolving from the hoisted copy. - 80dc1c1: Fail
blume build(exit 1) when any page fails frontmatter validation, instead of silently dropping the invalid pages and reporting a green build. Pass--no-strictto restore the old lenient behavior — the build then warns how many pages were dropped instead of printing an unqualified success. - 7c75bb8: Warn when two sidebar siblings (pages or folders) resolve to the same explicit or numeric
order, instead of silently falling back to an alphabetical tiebreak. Reported asBLUME_DUPLICATE_SIDEBAR_ORDERfromblume doctor/blume checkand any other diagnostics consumer.
July 19, 2026
Minor Changes
-
9fec53f: Add
blume audit, an offline site audit that replaces a hosted SEO crawler.blume auditreads the builtdist/HTML, joins each page back to the.mdxit came from, and reports SEO and site-health issues that name both the URL that is wrong and the front matter line that fixes it:⚠ Meta description too long or too short 5 pages /docs/configuration/export content/docs/configuration/export.mdx:3 fix: Rewrite `description` in the frontmatter to fit the length range.It runs 87 checks across content, duplicates, indexability, links, redirects, social tags, localization, assets, sitemap, robots.txt, structured data, and AI discovery. Findings are rolled up by check rather than dumped per page, and any tier that didn’t run says so. The check set deliberately skips things that can’t happen to an Astro-built site (missing hashed bundles,
rel=nofollow) in favor of checks a crawler can’t do — broken#fragmentanchors,draft: truepages that shipped,llms.txtheld to the sitemap’s standard, and Open Graph images verified as bytes.Flags:
--url <origin>also probes a live deployment for whatdist/can’t show (bad rewrites, missing compression,X-Robots-Tagdeindexing).--externalprobes outbound links.--claude/--codexwrite the JSON report and open the agent interactively to fix each finding at its source.--fail-on <severity>(defaulterror) as the CI gate, plus--only/--skip,--json,--verbose, and--list-checks.
blume validateis unchanged — it remains the fast source-level link check that needs no build.
Patch Changes
-
3aee378: Strip trailing slashes from the deploy adapter root with a linear scan instead of a
/\/+$/regex. The old pattern could backtrack polynomially on a root path containing long runs of/(CodeQLjs/polynomial-redos); the new trim is O(n) and yields the same single-trailing-slash directory URL. -
f779fd5: Make
blume audit --claude/--codexwork on Windows: npm installs the agent CLIs as.cmdshims that Node only runs through a shell, and cmd.exe can’t carry the multi-line prompt as an argument — the handoff now writes the prompt to a file and launches the shim with a one-line pointer, and a missing executable still gets the install hint instead of a raw ENOENT -
d99726a: Account for
deployment.basethroughout the audit: link, sitemap, hreflang, llms.txt, asset, and og:image checks now strip the deployment base from emitted URLs before comparing them to the built file tree, instead of reporting every internal link and sitemap entry as broken on subpath deploys -
d99726a: Make the audit’s double-slash check able to fire on the case it was written for: an href like
//docs/xfrom a trailing-slash base is now flagged (once per target) instead of being silently skipped as a protocol-relative external link -
d99726a: Replace a raw NUL byte in the audit’s duplicate-content grouping key with the
\u0000escape, so the shipped source is valid text (git diffed it as binary and grep skipped it) -
d99726a: Exempt the home page from the audit’s orphan-page check when it lives under a
basePath—/docswas reported as an orphan even though/never is -
d99726a: Stop reporting a redirect to a served static file (
/old-whitepaper→/files/whitepaper.pdf) as broken — the audit now resolves redirects against files as well as pages -
d99726a: Keep a gap between the URL and source-file columns in the audit report when a URL reaches the column width, instead of fusing them into one string
-
d99726a: Fix two robots.txt matching gaps in the audit:
Disallow: /docs*$now matches everything under/docs(the trailing wildcard absorbs the anchor), and trailing-slash rules likeDisallow: /page/are matched against the sitemap<loc>as served instead of a slash-stripped copy -
d99726a: Only report
ROBOTS_META_UNEXPECTEDwhen the robots meta actually blocks indexing — a page declaringindex, followis no longer claimed to “not be indexed” -
8d7e779: Format the changelog timeline’s dates in the configured locale. The timeline hardcoded
en, so an i18n site showed two languages at once: a page’s “last updated” stamp honored the locale while/changelogstayed English./changelogis an unlocalized route whose chrome already renders in the default locale, so its dates now follow that same locale. English sites are unaffected. -
d518958: Add
inlineandparamprops to theTabscomponent.inlinerenders borderless — a tab strip on a full-width rule with the content flowing beneath as prose — instead of the bordered box.paramsyncs the active tab to a URL query param instead of the hash; because each group owns its ownparam, severalTabscan share a page and every selection is deep-linkable (a link ending in?install=windowsopens on that tab). Existing boxed, hash-syncedTabsandCodeGroupare unchanged. -
d9dfdba: Stop shipping underscore-prefixed
.astrofiles inpages/as routes. Blume injects user pages itself and globbed every.astrofile, so private partials — shared layouts and home-page sections likepages/_home/Hero.astroorpages/_FeatureBrowser.astro— were each built into their own HTML page. Page discovery now honors Astro’s convention: any file or folder whose name starts with_stays importable but is never routed. -
437ce03: Fix
blume devunder pnpm’s default isolated linker. The generated runtime now checks Astro through the same physicalnode_modulesancestor lookup used by its ESM config, instead of mistaking pnpm’s CommonJS-onlyNODE_PATHexposure for a resolvableastro/configimport. -
9aad3e6: Add
frontmatter.extend: opt-in custom frontmatter keys, each validated by a user-supplied schema. Page frontmatter stays strictly validated by default; a project can now declare extra keys (e.g.owner,reviewedAt) inblume.config.ts, mapped to schemas consumed through the Standard Schema interface — so Zod (any version the project installs), Valibot, and ArkType all work. Declared keys are validated on every page (mark them.optional()to relax), validated values are preserved on each page record’scustomfield, and every other key keeps the strict typo-catching behavior. -
368b258: Emit a
_headersfile for static builds so hosts serve the raw AI-ready endpoints with an explicitcharset=utf-8. Blume’s/<route>.md,/<route>.mdx, and.txtoutputs (llms.txt,llms-full.txt) are valid UTF-8, but common static hosts serve them astext/markdown/text/plainwith no charset — so browsers fall back to Windows-1252 and non-ASCII docs (Japanese, accented Latin, …) render as mojibake when the raw URL is opened directly. The new_headerspins the samecharset=utf-8Content-Type the dev/server runtime already sends; Netlify and Cloudflare (Pages/Workers static assets) honor it, and hosts that ignore_headers(Vercel, S3) are unaffected. The globs carry anydeployment.base/basePathstack, and a_headersyou ship inpublic/is left untouched — exactly like_redirects. -
d99726a: Prefix the
_headers.txtcharset rule with onlydeployment.base—llms.txt/llms-full.txtare served at the deploy root, so abasePathdeployment shipped a/docs/*.txtrule that matched nothing and left the mojibake fix inert -
a4453e4: Upgrade Takumi to v2 via takumi-js. Emoji in titles now render as Twemoji glyphs, fetched once per glyph per build. The OG card palette accepts any CSS color, matching
theme.accent— a color the renderer can’t parse now fails the build instead of silently falling back.renderOgImage(exported fromblume/og) now returns aUint8Arrayrather than aBuffer. -
548bdd7: Link the RSS feeds from
llms.txt. The generated index mirrored the docs navigation but never referenced the per-content-type feeds (e.g./blog/rss.xml), so an agent readingllms.txthad no pointer to fresh blog posts or changelog entries. The index now closes with an## RSS Feedssection listing each configured feed that has pages, under the same condition the feeds themselves exist — RSS enabled and an absolutedeployment.site— and carrying anydeployment.basesubpath. This mirrors theartifacts.feedslist already emitted inagent-readability.json. -
d99726a: Fix a data-loss bug in the blume-migrate Mintlify codemod: renaming a key into a parent block that appears earlier in the frontmatter (e.g.
canonicalinto an existingseo:) deleted the wrong line and left the source key behind; icon remaps also now preserve trailing comments, and the skill references no longer document the pre-1.0.3 top-levelmcpconfig -
8769dd1: Add
seo.og.fontsto load Google Font families into the Open Graph card renderer. Takumi’s built-in font covers only Latin, so a non-Latin page or site title (CJK, and so on) rendered as tofu with no way to fix it. List the families by name — bare strings, or{ name, weight, style }for weight/style — and Blume fetches them from Google Fonts at build via Takumi’sgoogleFontshelper, registering only the glyph subsets each title uses. Latin text renders unchanged. -
d99726a: Add
seo.og.fontsto theOgConfigauthoring type — the schema accepted it but TypeScript rejected it inblume.config.ts, making the documented CJK/tofu fix untypeable — and correct the palette doc comment to say any CSS color works, not just hex -
de6403c: Fix the OG image build failing with “Cannot find native binding” on Vercel (Linux). The
googleFontsOG-font loader is imported fromtakumi-js/helpers, but only the baretakumi-jswas externalized for the static-prerender Vite environment — which matches by exact specifier, so the subpath (and the native@takumi-rs/corebackend it pulls in) got bundled into the prerender chunk, relocating the.nodebinding lookup. Externalizetakumi-js/helpersand the@takumi-rs/*packages so the native backend always resolves fromnode_modulesat runtime. -
bb9737e: Render authentication requirements in the native OpenAPI reference. Operations that declare security requirements — their own
security, or the document’s root default — now show an Authorization section above their parameters: the credential’s carrier (Authorizationheader, API-key header/query/cookie), a human label per scheme type (Bearer token, Basic auth, API key, OAuth2, OpenID Connect, Mutual TLS), the scheme’s description, and OAuth scopes. Multiple requirement alternatives render as “or” groups (schemes within one requirement are required together), an empty{}requirement marks auth as optional, andsecurity: []on an operation keeps it public with no section. The generated code samples now send a matching placeholder credential (e.g.-H "Authorization: Bearer YOUR_TOKEN"), with a spec-declared explicit header parameter still taking precedence, and a query-borne API key appended to the sample URL. Previously the renderer ignoredsecurityentirely, so authenticated endpoints were indistinguishable from public ones. -
d99726a: Align the accessible heading level of the Authorization and Parameters sections with Request body and Responses on API reference operation pages
-
d99726a: Stop duplicating a query API key in request samples when the spec also declares the credential as an explicit query parameter — the parameter’s own example now wins, matching the header behavior
-
e6be2f6: Match unordered-list bullets to ordered-list numbers in prose.
--tw-prose-bulletsnow defaults to--blume-muted-foreground(like--tw-prose-counters) instead of--blume-border, which rendered bullets much lighter than the numbers beside them. -
d99726a: Normalize
deployment.basebefore composing it into redirect targets — a trailing-slash (/base/) or bare (base) value produced/base//newor relative destinations in Astro redirects and the platform redirect files -
93ea41b: Fix redirects escaping the site under
deployment.base. A redirect’stowas only ever rewritten withbasePath, never with the deployment base, so withbase: "/docs"ato: "/new"emitted a redirect to/new— outside the base, 404ing on a subpath deploy (GitHub Pages project sites, most commonly) with no build-time error. Astro appliesbasewhen it builds the match pattern forfrom, but resolves a destination either by regenerating it from a matching route’s segments (which carry no base) or by passing it through verbatim — neither prependsbase, so Blume now applies it totoitself. The two bases also compose correctly:deployment.baseandbasePathset together stack as{base}/{basePath}, which the old front-prepend could not produce in that order.The static host redirect files (
_redirects,vercel.json,blume-redirects.json) had the mirror-image bug on the other side: they are matched against the real served URL, butfromwas written without the deployment base, so it never matched. Both sides now carry the full stack. Redirects are authored root-relative in every case, and a base already written intotoby hand is preserved rather than doubled. -
d99726a: Warn when a
search.popularlink uses an image or inline-SVG icon — the client search island can only render built-in icon names, and the silent fallback to the file glyph was exactly what the validator was meant to catch -
1c18379: Add
search.popularto curate the Cmd+K empty-state link list. When set, each{ href, label, icon? }entry replaces the default first-six sidebar pages — useful on multi-tab sites where sidebar order surfaces the wrong section. Eachhrefis authored root-relative and picks upbasePathautomatically (external URLs pass through);icontakes a built-in icon name and defaults to a file glyph. Omit or leave empty to keep the sidebar fallback. -
ea4c560: Stop headings inside a
<Prompt>block from leaking into the page’s table of contents.Prompt.astrorenders its children into a permanentlyhiddennode (used only to build the copy-to-clipboard and Cursor-deeplink text), butextractHeadingshad no way to know that — any##inside a<Prompt>was extracted as a real page heading and appeared in the “On this page” sidebar, linking to content that never renders visibly. Heading extraction now tracks<Prompt>/</Prompt>nesting depth the same way fenced code blocks already are, and skips headings while inside one. Tag detection is anchored to line starts — block-level JSX in MDX starts its own line — so a prose or heading mention of<Prompt>never opens a hidden region, and a tag whose attributes span several lines only counts once its closing>shows it isn’t self-closing. -
c6ca54a: Add a themeable content-column width. A new
--blume-content-widthtoken (default42rem) is exposed as amax-w-contentutility through Tailwind’s--container-contenttheme key, and the article, breadcrumb, mobile table of contents, page feedback, pagination, and last-updated line now use it instead of hardcoding42rem. Override--blume-content-widthto re-measure the whole column at once; the default is unchanged. -
31ee259: Bundle the Vercel serverless function with its chunks, virtual middleware, and dependencies. With
deployment.output: "server"andadapter: "vercel", the render function (.vercel/output/functions/_render.func) shipped asentry.mjsalone, so any server-rendered request — the Docs MCP endpoint, Ask AI — 500’d at runtime withERR_MODULE_NOT_FOUND. The adapter resolves both its Build Output tree and its@vercel/nftdependency trace against Astro’sroot, which Blume points at the hidden.blumeruntime; the trace’s base then excluded the server bundle (which lives underoutDir, outside.blume) and collapsed to a single file. The adapter is now shown the real project root, so the trace covers the function’s chunks andnode_modulesand the output lands at the project root natively. Projects inside a workspace were unaffected — nft’s base search climbed past.blumeto the workspace root — so this only ever broke standalone projects.
July 15, 2026
Patch Changes
-
cf8fa22: Fix the spacing inside directive callouts (
:::note,:::success, …). The global prose paragraph rule leaks a 1rem margin onto the callout’s paragraphs even though the callout isnot-prose, and with a title the body paragraph isn’t the first child — so that margin stacked under the title’s own gap and left a too-large space between the title and the body. The Callout now overrides that margin locally for a uniform, compact gap between the title, paragraphs, and lists. Callouts without a title and normal prose spacing are unchanged. -
bb5944d: Gate the language icon on
data-languageso it no longer overlaps the first code line on header-less standalone<CodeBlock>s. The icon transformer runs whenever icons are on, but a standalone block without atitlenever getsdata-language(the header bar that reserves the icon’s space), so the absolutely-positioned icon sat on top of the first line. Fenced code and titled blocks are unaffected. -
d59be0a: API references (OpenAPI, AsyncAPI) no longer add a header tab automatically. Point a
navigation.tabsentry at the reference route to surface it — this also lets you control the tab label and scopes the operations sidebar for the native renderer. -
f02b94e: Introduce the
blume-migrateagent skill. It teaches an AI agent to migrate an existing docs site — Mintlify, Docusaurus, Fumadocs, Nextra, Starlight, or any docs framework — into an idiomatic Blume project: translating the source config toblume.config.ts, restructuring content into filesystem-derived navigation (withredirectsfor every moved route), rewriting callouts to:::directives, converting icons to Lucide, inlining snippets, and pointing generated API references atopenapi.sourcesinstead of porting endpoint stubs. Ships with per-framework mapping references, a deterministic Mintlify codemod for the icon/frontmatter pass, and monorepo/Vercel integration recipes. The skill is bundled in the package underskills/. -
a421a1e: Make the generated runtime type-check cleanly under
blume check --strict --isolated(and any project whosetsconfigincludes the generated files). Previously a valid site could fail with dozens of errors in the generated.blume-verifyfiles:- The raw-Markdown, RSS, and OG endpoints imported
data.jsondirectly, so TypeScript widened the JSON (navigationkindtostring, empty arrays tonever[], theme mode tostring) and rejected it against Blume’s own types. They now import the typedblume:datavirtual module. - Endpoint handlers (
GET,getStaticPathscallbacks) and the changelog/content-page helper functions had implicit-anyparameters; they now carry explicit types. - The OG endpoint’s PNG
Bufferis wrapped in aUint8Arrayso it satisfies theResponsebody type, and the empty component-overrides module and theblume:examples/blume:examples-themevirtual modules now declare types.
Also stops
blume check --isolatedfrom reloading a runningblume devserver:.blume-verifyis now in Blume’s ignored-directory set, so generating the isolated runtime no longer trips the content watcher. - The raw-Markdown, RSS, and OG endpoints imported
-
6a97cb2: Reserve space for local per-mode SVG logos before they load.
-
5793ccf: Render Mermaid diagrams in
blume dev. Mermaid statically imports dayjs as CommonJS (dayjs/dayjs.min.js), and in dev Vite served that dependency un-pre-bundled, so it exposed nodefaultexport and mermaid threwdoes not provide an export named 'default'— leaving diagrams blank (the production build already handled the interop). Mermaid now goes through Vite’s dependency optimizer, which bundles dayjs with correct CommonJS interop. Because Blume’simport("mermaid")lives insidenode_modules/blume— a path Vite’s optimizer scan doesn’t crawl in a standalone install — the diagram library was never discovered on its own, so it’s included explicitly via the nestedblume > mermaidform (mermaid isn’t a direct dependency of the generated project). -
5c0b0f0: Add custom logo and palette settings for generated Open Graph cards.
-
1bdc849: Truncate long page titles in the previous/next pagination links so they no longer overflow their pill container, and allow inline code inside table cells to wrap instead of forcing the whole table to overflow horizontally.
-
f2ffcb5: Prerender Cloudflare adapter builds in Node so build-time
node:imports resolve. Astro 6 changed the@astrojs/cloudflaredefault prerender runtime from Node to workerd, which broke Blume prerender on Cloudflare (No such module "node:path"andnode:fsusage in Blume’s build-time content tooling). The generatedastro.config.mjsnow passesprerenderEnvironment: "node"to the Cloudflare adapter. On-demand pages still run in workerd at request time. -
d11a90c: Stop the
Promptcomponent’s copy button from shifting the layout. On copy, the button’s label swapped “Copy prompt” → “Copied”, and because the button was sized to its text, it shrank — giving the description beside it more room and reflowing it (a two-line description would collapse to one line, then jump back). Both labels now share a single grid cell, so the button is always sized to the wider “Copy prompt” and never resizes when the state changes. -
fcdc3b3: Match native browser controls, including scrollbars, to the active color theme.
-
3795fbb: Keep root navigation tabs active on descendant routes unless a more specific tab matches.
-
e5aa042: Use the
rounded-blumeradius token instead of a hardcodedrounded-fullon thePageFeedbackandPaginationbuttons so they respect the configuredtheme.radius. -
32e29f0: Keep the text caret inside the search input on mobile. The search dialog was vertically centered, so when the on-screen keyboard opened and the viewport shrank, the dialog re-centered and the input moved up while the native caret stayed put — stranding the cursor below the input, over the results. The dialog is now top-anchored on small screens (like DocSearch/cmdk) so the input sits above where the keyboard appears and doesn’t move, and its height is capped to the dynamic viewport so it fits above the keyboard. Desktop keeps its centered layout.
-
7806565: Resolve a section tab’s link to its first page when the section has no index page, so the tab no longer 404s. A tab’s
pathstill scopes its sidebar section and matches the active tab, but the clickable target now falls back to the first page in the section (sidebar order) when nothing lives at the path itself — e.g./exampleswith only/examples/hello-worldlinks to that page instead of a missing/examples. -
fe21c54: Wrap markdown tables in a horizontal-scroll container so wide tables no longer overflow the prose column. The wrapper is framed with a rounded border and cell padding, and header labels stay on one line, so a clipped wide table reads as scrollable rather than cut off.
July 13, 2026
Patch Changes
- e2f902c: Render the Ask AI trigger from the shared header instead of wiring it up per page. Custom pages built on
PageLayout(a landing page, most of all) never passed the header’saskslot, so the Ask AI button — and the search modal’s hand-off to it — silently went missing on them while the generated docs, changelog, and reference pages had it. The header now owns the trigger and reads whether Ask AI is on from the config, so every page gets it; passaskEnabled={false}to opt a page out. - 66b721b: Move the MCP server config under
aiinblume.config.ts, alongside the other agent-facing features. Renamemcp: { … }toai: { mcp: { … } }— the shape of the block is unchanged. - c8ae77e: Fix
ERR_MODULE_NOT_FOUNDin a deployed server function. Surfacing an adapter’s deploy bundle out of.blumeresolved every traced dependency’s symlink against the source dir, so the links pointed into a directory the same step then deleted — the function died on its first external import (Cannot find package '@orama/orama'with Ask AI or the MCP server enabled). The bundle is now copied verbatim, leaving those links relative and internal to it.
July 13, 2026
Patch Changes
-
d62dbd0: Harden the pre-paint inline scripts and the route-normalizing regexes against the issues CodeQL flagged.
The theme and banner scripts in
<head>used to be built by interpolating config values into JavaScript source withJSON.stringify. JSON escaping isn’t a code-context escape —</script>and U+2028/U+2029 pass straight through it — so a craftedbanner.idor theme value could break out of the script. Both scripts are now constants and take their values fromdata-*attributes on their own<script>tag, which Astro HTML-escapes.ReferenceLayouthad drifted to its own inline copies of both scripts; it now shares the same module asRootLayoutandPageLayout.Route and slug normalization used
/^\/+|\/+$/-style patterns to trim leading and trailing separators. Those take quadratic time on a long run of the trimmed character, and they run on values that come from outside Blume (configured routes, OpenAPI spec URLs, the site origin), so a pathological config could hang the build. They’re replaced by linear trimming helpers incore/trim.ts; behavior is unchanged.
July 13, 2026
Patch Changes
- 6afb56a: Declare the
blumebin asbin/blume.mjsinstead of./bin/blume.mjs. The leading./is redundant and some package managers normalize it away when linking the binary; dropping it keeps the published manifest consistent with what installers actually write.
July 13, 2026
Major Changes
- 7372fe8: Blume v1.
Patch Changes
-
e922ff5: Stop “Clear conversation” from resurrecting an orphaned answer bubble while a reply is still streaming. Clearing mid-answer emptied the panel, but the in-flight stream kept re-appending its assistant message to the cleared conversation — a growing answer with no question above it. Clearing now aborts the in-flight request and revokes the stream’s right to write into the conversation, and asking a new question right after a clear works as before.
-
e922ff5: Match locale folders and dot suffixes case-insensitively, the way BCP 47 codes are defined. A configured
pt-BRwith the conventional lowercasept-br/folder (or anintro.pt-br.mdxsuffix under thedotparser) previously fell through as default-locale content at a literal/pt-br/…route — and the unconfigured-locale warning, which already compared case-insensitively, stayed silent about it. Those files now route as the configured locale, with the configured casing in routes and labels. -
e922ff5: The generated changelog index’s heading, page title, and meta description now come from the translatable
changelog.titleandchangelog.descriptionUI strings (translated in every built-in pack), joining the reveal button the template already localized. Previously the page rendered a hardcoded English “Changelog” heading, an English “ changelog“ title suffix, and an English description even on non-English default locales. -
1e5446f: Lengthen the default changelog description. “Product updates and release notes.” is 34 characters — under the 50-character floor search engines want from a meta description, and the generated
/changelogpage uses it for both its meta tag and its on-page subtitle. It now reads “Product updates, new features, and fixes from every release.” Sites that setui.changelog.descriptionare unaffected. -
e922ff5: The syntax docs now label callouts and
package-installblocks as MDX-only, matching the existing notes on diagrams and math. Previously a reader following the page could write:::noteor apackage-installfence in a.mdfile and get literal text or a plain code fence with no hint why. -
e922ff5: Apply the js-yaml 4-safe YAML engine to
matter.readas well. The front-matter wrapper previously exposed gray-matter’s ownreadhelper unwrapped, so reading a file through it would parse with the removedsafeLoaddefault and crash with “Function yaml.safeLoad is removed in js-yaml 4” — the exact failure the wrapper exists to prevent formatter()andmatter.stringify(). -
68520af: Exclude Vite’s pre-bundled dep cache (
node_modules/.vite/) from @vitejs/plugin-react. Astro’s react() replaces the plugin’s defaultnode_modulesexclude, so Babel (carrying the React Compiler) was re-parsing every 500KB+ optimized dep chunk in dev — the source of the “[BABEL] Note: The code generator has deoptimised the styling” messages. Blume’s own components stay covered by the compiler. -
e922ff5: Serve image-path icons from under
deployment.base.<Icon>emitted an image icon’s path (/brand/mark.png) as-is, so on a site deployed under a base path the request went to the domain root and 404’d while<Card img>, the logo, and every other image emitter were correctly rebased. Image icons now get the samewithBasetreatment; external URLs, data URIs, and relative paths are untouched. -
e922ff5:
@astrojs/vueand@astrojs/svelteare now declared as optional peer dependencies, matching@astrojs/netlifyand@astrojs/cloudflare. Projects using Vue or Svelte islands must install the matching integration themselves, and package managers now surface and satisfy that requirement instead of the build relying on an undeclared package. -
e922ff5:
blume build --isolatednow reports the build’s actual output directory on success. Aserveroutput build with the Vercel adapter lands its deploy bundle at.blume-verify/.vercel/output(it is never surfaced to the project root), but the message previously pointed at.blume-verify/dist, which that build never populates. -
e922ff5: The breadcrumb and pagination
<nav>landmark labels are now translatable via the newnav.breadcrumbandpage.paginationUI strings, with translations in every built-in language pack. Previously both were hardcoded English (“Breadcrumb”, “Pagination”) while the sibling chrome landmarks were dictionary-driven, so screen readers announced English landmark names on localized sites. -
e922ff5: Fix
ai.markdownComponentscrashing config validation in projects that resolve Zod 4. The schema used the single-argumentz.record(...)form, which Zod 4 rejects at schema-construction time, so any config parse failed before your settings were even read; it now uses the dual-compatible two-argument form. -
e922ff5: The MCP
get_pagetool’s description — shipped user-facing intools/listand the server card — no longer claims to return a page’s original Markdown source. The tool serves the agent variant (components downleveled to plain Markdown,<Visibility>resolved for agents), and the description now says so. -
e922ff5: The sidebar’s panel-stack back arrow and drill-in chevrons now mirror under RTL locales (
rtl:-scale-x-100), matching the pagination arrows and the panel slide animation, which already flipped direction. Previously the arrows pointed against the reading direction on RTL sites. -
d9590fc: Emit a complete Open Graph card in the page head, so crawlers and social validators stop flagging the metadata as incomplete:
og:url— the page’s canonical URL (rendered only whendeployment.siteis set, or a page overridesseo.canonical).og:type—articleon blog posts and changelog entries,websiteeverywhere else. Article pages also emitarticle:published_timeandarticle:modified_time.og:site_name— the sitetitle.og:image:width,og:image:height,og:image:type, andog:image:alton Blume’s generated OG card, so a crawler can lay it out without fetching the image. Anseo.imageyou supply yourself declares none of these, since its dimensions and format are unknown.
-
1e5446f: Give every generated OpenAPI page its own meta description. Operation and overview pages set no
description, so all of them fell back to the site-wide default — a spec with twenty operations shipped twenty pages carrying one identical description, which search engines treat as duplicate content. Each operation page now derives a description from the spec’s own prose (its description, or its summary) followed by the endpoint it documents, and the overview page uses the spec description. These land inseo.description, so they feed the meta tag without also printing as a visible subtitle above the body prose. -
80eb252: Emit X (Twitter) card tags, and add
seo.xfor account attribution:seo: { x: { handle: "@acme", creator: "@jane" }, }handlebecomestwitter:siteandcreatorbecomestwitter:creator— the one piece of X card metadata with no Open Graph equivalent to fall back to. A page can credit its own author withseo.x.creatorfrontmatter, which is what a guest post wants. The@is optional in both places.Every page also now emits
twitter:card,twitter:title,twitter:description, andtwitter:image:alton the generated card.twitter:cardpreviously rendered only when a page had an image; a page without one now gets the compactsummarycard instead of sharing as a bare link. -
e922ff5: The Scalar API reference shell now sets
<html lang>anddirfrom the default locale, mirroring the changelog index’s locale wiring, and renders the same localized skip-to-content link as the other layouts. Previously it hardcodedlang="en"with nodir— so RTL default locales rendered LTR chrome — and offered keyboard users no way to skip past the navbar. -
e922ff5: The search dialog’s section-filter “All” pill is now translatable via the new
search.allUI string, with translations in every built-in language pack. Previously it rendered hardcoded English inside an otherwise fully localized dialog. -
e922ff5: ⌘K / Ctrl+K now toggles the search dialog: pressing it while the dialog is open closes it, matching how ⌘I toggles the Ask AI panel. Previously the shortcut unconditionally re-opened, calling
showModal()on an already-open dialog — a silent no-op on evergreen browsers but anInvalidStateErroron older engines. The/shortcut stays open-only, andopen()itself now guards against an already-open dialog. -
e922ff5: Reject a
tocconfig whoseminHeadingLevelexceeds itsmaxHeadingLevel. An inverted range (including an explicit min above the default max of 3) previously validated fine and silently rendered an empty table of contents on every page; it now fails config validation with a clear message. -
e922ff5: Internal-error stack traces now relativize
.blume/runtime frames on Windows too. The remap previously matched only POSIX absolute paths, so drive-letter frames likeC:\...\.blume\...printed the full machine path instead of the project-relative.blume\...form tagged(generated).