18a35d0: Send an X-Powered-By: Blume response header from blume dev and from every host Blume sets headers on. Set poweredBy: false to leave it out.
7afeed7: A cloudflare() server build no longer writes dist/server/node_modules. The Worker bundles everything it imports, so the links to Blume’s dependencies there were never loaded, but a copy of dist/ that followed them (such as a CI artifact) uploaded every dependency as a Worker module and failed the deploy on the 64 MiB size limit. dist/ now deploys the same wherever it’s copied.
4d65f16: Add a clics() analytics adapter. It renders the Clics tracker tag with projectId as data-project-id, and the tracker counts pageviews (client-side navigations included) and outbound-link clicks. allowLocalhost and disableOutboundLinks set the tracker’s matching flags. Blume’s custom events don’t reach Clics, whose tracker has no global event API.
44616f3: The homepage now emits a WebPage JSON-LD node with its description, language, and dates, including each locale’s homepage, the root of a basePath, and sites without deployment.site. PageLayout takes a lastModified prop to date a custom homepage, and the “Last updated” date on every page is wrapped in a <time> element, so search engines no longer pick up an unrelated date from the page.
858c203: A zoomed image now closes with Escape, a click, or a scroll when the visitor’s system asks for reduced motion. The zoom still opens and closes without animating, but it no longer stays stuck open until the page reloads.
9d3897a: An API reference page whose description opens with the lead-in to a list (“Supports two modes:”) no longer ends its meta description in a dangling : or :.. The list’s items now follow the lead-in (“Supports two modes: Explicit IDs; Select all.”), on operation and overview pages and with seoDescriptionSuffix off. A lead-in to anything else, such as a code block, closes with a period, and a colon that belongs to the text, like inline code ending in tenant:, stays. Operation summaries on the agent surfaces close the same way.
d1cf8d9: A build without the narration provider’s key, such as a pull request from a fork, now still ships the generated clips its cache restored: only pages that need a new clip fall back to browser voices, and the warning prints only when a clip is missing.
d1cf8d9: Narration can generate voices with openai() from blume/ai, not only gateway(). narration: { provider: openai({ model, voice }) } uses OpenAI’s speech models, and with a baseUrl any server that serves OpenAI’s /audio/speech endpoint, such as a self-hosted Kokoro-FastAPI, Speaches, or LiteLLM, so a site whose build can’t reach a hosted service still gets generated voices. With a baseUrl, only the key apiKeyEnv names is sent, so OPENAI_API_KEY never reaches another server, and with none named the build calls the server without a bearer token instead of skipping generation. It speaks through @ai-sdk/openai, which blume build and blume doctor report when it isn’t installed. A response that isn’t MP3 fails its clip instead of being cached. Clips are cached per server and shared across languages, since OpenAI’s speech API takes none, and gateway() clips keep their cache keys.
b67d679: notion() takes mdx: true to read the text of a Notion page as MDX, so components like <CardGroup> and <Card>, expressions, and Markdown typed into paragraphs, headings, list items, quotes, and callouts render instead of showing as written. Blume 2 escapes that text by default, which left pages written this way under Blume 1 showing their tags. Code blocks, toggle titles, captions, and properties stay plain text. MDX runs code at build time, so turn this on only for a database whose editors you trust with the site’s build.
cfb5c51: The blume-migrate skill’s bundled oxfmt patch now targets oxfmt 0.71.0, so a migrated repo that formats with Ultracite keeps its ::: directives, titled fences like :::warning[Heads up] included, on the current oxfmt release.
d23dda7: In page display mode, a sidebar group with an index page now links its row’s name to that page, as group and flat mode already did: clicking the name opens the section’s landing page with its panel open, even when the index page sets sidebar.hidden: true. Before, the row only slid the panel in and left the previous page on screen. The chevron beside the name still slides the panel in without leaving the page, and a group without an index page keeps its whole row as the drill-in button. The row of the section holding the current page is now highlighted too, so after the back arrow the parent panel still shows where you are, and clicking the name of the section’s own page slides its panel back in without reloading.