Skip to content
Blume
Esc
↑↓navigate↵open⌘Jpreview
On this page

blume@2.2.0

Minor Changes

  • 3ba8dc7: blume migrate now moves sites from VitePress, VuePress, Docus, MkDocs (including Material for MkDocs and Zensical projects), mdBook, Fern, GitBook, Redocly, ReadMe, Docsify, Jekyll (Just the Docs), and GitHub wikis. Name the source (npx blume migrate gitbook --claude) or let Blume detect it from the project’s files; a GitHub wiki is always named. The bundled blume-migrate skill has a mapping reference for each one, plus codemods for VitePress, Docus, MkDocs, Fern, ReadMe, Docsify, Jekyll, and GitHub wikis that make the mechanical rewrites before the agent starts. Three helper scripts ship with it: operation-routes.mjs maps each OpenAPI endpoint to its Blume route for redirects, pin-heading-ids.mjs keeps a migrated site’s old heading anchors working, and include-excerpts.mjs generates the partial-file code excerpts (mdBook anchors, VitePress regions, line ranges) that <include> can’t select.
  • 0c4eed3: Blume now requires Node.js 22.19 or newer, up from 22.12. Blume uses undici to send remote API spec fetches through HTTP_PROXY or HTTPS_PROXY when either is set. That dependency is now undici 8, which requires Node.js 22.19 or newer. blume doctor warns when the running Node.js is older than that.
  • be943aa: Add a <Changelog /> component, so a hand-written /changelog page can put an introduction or a feed link above the generated release list instead of rebuilding the list by hand. Place the tag in changelog/index.mdx with mode: center to match the generated page’s layout. It lists the same releases the generated page does, GitHub Releases entries included, and downlevels to that list in /changelog.md, llms-full.txt, MCP get_page, and search. The generated page now renders the same component.

Patch Changes

  • d68591a: Headings in an API spec’s descriptions no longer add <h1>s to reference pages. A spec’s info.description is often a document of its own under # Introduction and # Authentication headings, so the overview page had several <h1>s besides its title. Headings in info.description and in an operation’s description now move one level down (# renders as <h2>), and headings in a tag’s description two (# renders as <h3>, under the tag’s section).

  • ca9dc01: An API reference operation without a summary is now labeled in the sidebar by its method and path (GET /pets), as its page is already titled. Before, the label was the path alone, so GET /pets and POST /pets shared one, and an unedited spec failed blume validate --strict with BLUME_NAV_DUPLICATE_LABEL. AsyncAPI operations without a title or summary get their action and channel (SEND user/signup) the same way.

  • ca9dc01: An API reference operation’s URL now only has to be unique within its tag. Before, an operation whose slug another tag’s operation already used got its method added to the URL, so a list operation under a second tag lived at /reference/stores/list-get; it now lives at /reference/stores/list. Two operations with the same slug in one tag still get the method added to the second. The old URLs keep working: Blume redirects each one (and its .md and .mdx copies) to the new one with a 301, unless a page or a redirect you configured is already at that URL. <Operation id> values don’t change. In a graphql() reference, a type page whose name matches a root field moves the same way (/graphql/objects/pet-object to /graphql/objects/pet).

  • ca9dc01: API reference operations now follow the spec’s order in both the sidebar and the overview page: paths in the order the spec lists them, and each path’s methods in the order they’re written. Before, the sidebar sorted a tag’s operations alphabetically by label, and the overview listed each path’s methods in a fixed order (GET before POST, and so on). A meta.ts in a tag’s folder that sets pages still decides the sidebar order.

  • ca9dc01: API reference tag folders now split camelCase and PascalCase tag names into words, the way operation slugs already did: the tag InboxesThreads is served at /reference/inboxes-threads/… instead of /reference/inboxesthreads/…. The old URLs keep working: Blume redirects each one (and its .md and .mdx copies) to the page’s new URL with a 301, unless a page or a redirect you configured is already at that URL. This applies to openapi(), asyncapi() (including untagged operations grouped by channel address), and graphql() references.

  • 6db6cdd: blume audit --only and --skip now narrow the counts the report prints to the checks they leave in: the audit count in the summary, the audits count in --json, and the count beside each skipped tier. Before, blume audit --only redirects filtered the findings but still printed the counts for every check, so the filter looked ignored. A skipped tier the filter leaves no checks in isn’t listed.

  • 9b13c53: blume audit now checks where a pattern redirect sends its bare path. A pattern ending in /* or /:name* also matches the path without that segment: /mcp/* → /user-api/* sends /mcp to /user-api, and every host gets that rule. When /user-api isn’t a page, the audit now reports BLUME_AUDIT_REDIRECT_BROKEN for the pattern and names /mcp. A pattern that sends its bare path to itself, like /a/* → /a, is reported as BLUME_AUDIT_REDIRECT_LOOP. The audit also stops following a redirect chain that grows a segment at each hop, which used to keep it running forever, and reports the chain as a loop.

  • caf4d21: A meta.ts under a content root outside the project folder, like content: { root: "../docs" } or a sibling package in a monorepo, can now import { defineMeta } from "blume". Before, Blume looked for blume from the meta file’s own folder upward. A project that installs blume in its own node_modules failed every such file with BLUME_META_LOAD_FAILED: Cannot find module 'blume'. Blume now resolves blume and its subpaths (blume/sources, blume/schema, …) to the running Blume package wherever the file lives. The same fix applies to files that blume.config.ts imports from outside the project.

  • 000f0ac: blume validate now says why a link to a partial is broken. A file whose name, or a folder’s, starts with _ isn’t published, so a link like [setup](./_setup.md) reported only that no page resolves to /_setup, although the file is right there. The BLUME_BROKEN_LINK fix now names the file as a partial and suggests splicing it in with <include>, renaming it, or adding "!**/_*" to content.exclude.

  • c2e2a02: <Card img="./cover.png"> now finds an image next to the page, like ![](./cover.png) does. The card rendered img as written, so the browser resolved a relative path against the page’s URL and the image 404’d, while blume validate never checked it. A card’s relative img is now published with the page and points at its served copy, a card in an included partial resolves its image next to the partial, and the raw .mdx copy agents read points at the same URL. blume validate checks img like any image embed and reports a missing file as BLUME_BROKEN_ASSET.

  • 07ac505: A <CodeBlock> with no code prop, like a fenced block wrapped in <CodeBlock>…</CodeBlock> the way Fern and Mintlify write code, no longer fails the build with a bare TypeError that named no file or line. Without code, CodeBlock now renders its children as written, so the wrapped fence shows as it would on its own, and the page’s Markdown copy and llms-full.txt unwrap it the same way.

  • 1633bf9: Code blocks support // [!code error] and // [!code warning] notation comments, which tint a line red or amber. Before, those comments stayed in the code as written. Like the other notations, the comment is removed from the rendered block, and the new --blume-code-error and --blume-code-warning tokens (each with a -border partner) restyle the lines.

  • 8b05bef: blume dev, blume build, and blume check warn about code fences that don’t render as written. A language Shiki doesn’t know, like ```requirements, rendered as plain text with only a [Shiki] console line that named no page; it now warns as BLUME_UNKNOWN_CODE_LANGUAGE with the file and line. A fence option from another docs tool did nothing without a word: MkDocs’ hl_lines="2 3" and linenums="1", showLineNumbers, Mintlify’s lines, Fern’s wordWrap, and filename="…". Each now warns as BLUME_CODE_FENCE_OPTION and gives the Blume spelling ({2,3}, lineNumbers, wrap, title="…"). On a Rust block, rustdoc’s and mdBook’s ignore, no_run, should_panic, compile_fail, edition2015 through edition2024, noplayground, and editable warn the same way, with a note to remove them, since Blume never compiles, tests, or runs code. None of these words becomes part of the block’s title, so ```js app.js showLineNumbers is titled app.js.

  • 3f5ae80: A code block’s title is every word after its language. Before, only the first word counted, so ```javascript Install the client was titled “Install” and the rest of the line was dropped. Keywords like lineNumbers, line ranges, and key="value" options still stay out of the title. A title in brackets, like ```ts [file.ts] from Docus or a VitePress code group, now shows without its brackets, and a line range written against them, as in Docus’ ```ts [file.ts]{2}, highlights its lines and stays out of the title.

  • bb14a3a: An untitled code block in a <CodeGroup> is labeled by its language, like “Python” or “TypeScript”, instead of “Tab 1”, “Tab 2”. Groups still sync by label, so picking “Python” in one group picks it in the others. Code block headers name more languages the same way, like “Python”, “Go”, and “Rust” in place of their lowercase ids.

  • 1e1bb42: openapi() and graphql() now warn about codeSamples ids Blume doesn’t generate a sample for, instead of leaving them out of every operation page without a sign. BLUME_OPENAPI_UNKNOWN_CODE_SAMPLE (BLUME_GRAPHQL_UNKNOWN_CODE_SAMPLE for GraphQL) names the ids and lists the ones Blume accepts. ReadMe’s cplusplus is now accepted as an alias for cpp. Its objectivec has no generated sample, so it warns.

  • 194787d: The copy button on a ```console or ```shellsession block copies only the commands, without their prompts or output. Before, copying $ npm i blume and the lines it printed put all of it on the clipboard, so pasting it into a terminal ran $ and the output as commands. A command ending in \ keeps its continuation lines, and a block with no prompt copies as written.

  • a81a303: content.exclude (and a filesystem() source’s exclude) now adds to the default ["**/_*", "**/.*"] instead of replacing it. Before, setting exclude: ["drafts/**"] silently published every _-prefixed partial and dot-file as a page. To publish _-prefixed or dot-files on purpose, list the default you want dropped with a !: exclude: ["!**/_*"] publishes underscore files, and "!**/.*" dot-files. A config that already lists the two defaults beside its own patterns works as before.

  • 714fafc: A file or folder named with a day- or month-first date, or a year and month, now keeps its whole name in its URL. Blume read the first number as an ordering prefix, so changelog/12-05-2022.mdx published at /changelog/05-2022 and changelog/2024-01.mdx at /changelog/01, with no warning, and two files that differed only in that number collided on one route. Now 12-05-2022 (D-M-YYYY or M-D-YYYY) and 2024-01 (YYYY-MM) stay whole, like an ISO date (2024-01-05) already did, so those pages publish at /changelog/12-05-2022 and /changelog/2024-01. Each old URL redirects to the new one with a 301, its Markdown copies included, unless another page now lives there or a configured redirect already starts there.

  • d136cd5: blume dev, blume build, and blume check warn when a ::: container’s closing line has text after its colons. Inside a :::warning, a ::: card line closes the callout, so card was dropped from the page and the text after it fell outside the callout, with no warning. BLUME_DIRECTIVE_CLOSING_TEXT names the file and line and suggests a longer outer fence (::::warning … ::::) to keep the line inside the callout. A callout opener written with a space, like ::: tip from VitePress, VuePress, or Docusaurus v2, isn’t a directive and shows as text. Blume now warns about it as BLUME_DIRECTIVE_SPACED_NAME and gives the unspaced spelling, with a title moved into brackets (:::tip[Title]).

  • 3f8beb7: blume dev, blume build, and blume check warn when a callout opener has text after its name. :::tip Some title opens a tip callout but drops Some title, so the callout rendered untitled with no warning. BLUME_DIRECTIVE_OPENING_TEXT names the file and line and gives the bracketed spelling that keeps the title, :::tip[Some title].

  • 8b18782: A backslash-escaped < in an .mdx page no longer counts as a component tag. Text like Promise\<App> or \<Not set> renders as literal text, but blume dev and blume build still warned that <App> or <Not> isn’t a known component (BLUME_UNKNOWN_COMPONENT), and an escaped \<Component path="…"> was reported as a missing example. blume validate likewise no longer checks the link in an escaped \<a href="…"> or \<Card href="…">, or accepts a fragment that only an escaped \<a id="…"> provides. Real tags are still caught, including one after an escaped backslash (\\<Foo />).

  • ca181e4: Code fence languages are no longer case-sensitive: ```JSON and ```Dockerfile highlight like ```json and ```dockerfile, where before they fell back to plain text with only a console line. A line range written against the language, like ```js{2} (the VitePress and VuePress spelling), now highlights its lines and keeps the language, instead of the whole block falling back to plain text.

  • f6f9bfe: A relative link to a file next to a page now works. [the spec](./spec.pdf), a reference definition like [spec]: ./spec.pdf, and an HTML element naming such a file (the src of an <img>, <video>, <source>, or <audio>, and the href of an <a> or a component, as raw HTML in .md or an element in .mdx) all shipped as written, so the browser resolved them against the page’s URL and the file, never published, 404’d. The file is now published with the page and the built link points at that copy, keeping a suffix like #page=2, in the page’s Markdown copy and llms-full.txt too. A partial’s link moves with it when the partial is included from another folder. blume validate accepts these links, and no longer warns that an <img src> naming a file beside the page isn’t published.

  • 059c83f: blume dev, blume build, and blume doctor warn about folder meta that does nothing. A meta.ts pages entry that names no page or folder in its group was ignored without a word; BLUME_META_UNKNOWN_PAGE now names the file, the entry, and its line, and suggests the slug it most likely means ("quickstart" for "01-quickstart.mdx", or a near miss). A meta.ts outside every include glob of its content source is never read, which left a reference’s tag-folder meta.ts silently ignored on a site whose include lists only its own folders. When such a file sits in a sidebar group’s folder, BLUME_META_OUTSIDE_INCLUDE now says so and suggests an include glob that reaches it.

  • b04f1b7: A meta.ts in an OpenAPI, AsyncAPI, or GraphQL reference’s tag folder now changes only the fields it sets. Before, it replaced the group’s generated meta entirely, so a meta.ts that only renamed a tag group also dropped the group’s place in the spec’s tag order, and the group moved to the end of the sidebar. The generated title and order now fill in whatever the file leaves out, for meta.ts and meta.$.ts alike.

  • 983ac72: GitHub alerts render as callouts in .mdx. A quote that opens with [!NOTE], [!TIP], [!IMPORTANT], [!WARNING], or [!CAUTION] becomes a note, tip, note, warning, or danger callout, with any text after the marker as its title; before, it rendered as a plain quote that showed the marker as text. In an .md page, which renders no components, the quote stays a quote, and blume dev, blume build, and blume check now warn about it as BLUME_MD_GITHUB_ALERT so you can rename the page to .mdx.

    Callout and accordion titles keep their inline formatting. A :::note[Use **bold** and `code`] title, a <Callout title>, and an <AccordionItem> or <Expandable> title now render their code spans, emphasis, and links instead of flattening them to plain text or showing the Markdown as written. A title without any of that renders exactly as before, and an accordion keeps the id its title always gave it.

  • f50c9c6: Heading anchors no longer pick up stray dashes or badge text. A heading that ends or starts with a component, an image, or raw HTML slugged the space beside it into its id (## Maintainers <Badge/> anchored as #maintainers-), and one ending in a symbol after a space kept a trailing dash (## Features ✨ as #features-). Those ids now drop the dash: #maintainers, #features. A <Badge>’s text is no longer part of a heading’s id or its table of contents entry, so ## Install <Badge>beta</Badge> anchors as #install instead of #install-beta and lists as “Install”. A fragment can’t be redirected, so links to the old anchors land at the top of the page; blume validate reports the ones in your own pages.

    A heading that holds its own empty anchor, like GitBook’s ## Title <a href="#x" id="x"></a> or a wiki’s <a name="x"></a>, now takes that anchor’s id (or name) as its id and keeps a single self-link. Before, the anchor nested inside the heading’s link in .md (invalid HTML, with an id ending in a dash) and stopped the self-link from rendering in .mdx. A raw <a> link in an .md heading no longer gets the self-link wrapped around it either.

    The spaced { #id } and kramdown {: #id } heading markers that MkDocs writes, and an attribute list that sets the id beside classes or attributes ({ #id .wide }), stay in the heading’s text in .md, where only the unspaced {#id} pins an anchor. blume dev, blume build, and blume check now warn about them as BLUME_MD_CURLY_ANCHOR, naming the file and line and the {#id} or [#id] spelling to use, instead of silently giving the heading an id like setup--setup.

  • 5c1974b: Hiding the home page with sidebar.hidden (or hidden) to drop its sidebar row no longer takes it out of the sitemap, site search, llms.txt, the site skill, the MCP server’s page list, or the JSON API. The site’s root URL always serves the home page, so it stays listed, as a hidden folder index page that its group row links already did. This applies to each locale’s and archived version’s home page too.

  • 2a23248: The top-level noindex: true frontmatter shorthand now emits the page’s <meta name="robots" content="noindex"> and drops its canonical and structured data, exactly like seo: { noindex: true }. Before, it only took the page out of the sitemap, so blume audit reported the page as indexable but missing from the sitemap. The top-level hidden shorthand now also keeps a changelog entry off the /changelog index, like sidebar.hidden.

    Hiding a folder’s index page with sidebar.hidden (or hidden) to drop its duplicate sidebar row no longer takes the page out of the sitemap, site search, llms.txt, the site skill, the MCP server’s page list, or the JSON API. The group row still links the landing page, so it stays listed like any other page; a hidden page nothing in the sidebar links is still left out.

    After upgrading, the first build re-reads every page instead of reusing pages cached by the previous Blume version.

  • a05c4d6: An image in an included partial now resolves wherever the partial is spliced, however its path is written. Only a plain path was rebased onto the including page, so ![](<./my diagram.png>), ![](./a.png 'Title'), or ![]( ./a.png ) kept the partial’s path and broke the page that included it (the build failed to resolve the image, or the agent-facing Markdown pointed nowhere). Every form a Markdown image’s destination can take is now rebased and written back in its own form, and colocated images in those forms are served to agents the same way. blume validate reads those link forms too: a link with spaces around its destination, or a title in single quotes or parentheses, used to go unchecked.

  • 153a87d: Link reference definitions now cross <include>, as if the partial were written inline. A reference in a page ([PEP 508]) to a definition in an included partial ([pep 508]: https://…), or a reference in a partial to a definition in the page or in another partial, stayed literal text, so a partial holding a site’s shared link definitions linked nothing. Each now resolves, in .md and .mdx alike. A partial’s definition of a file beside it moves with the partial, like its links. When the page and a partial both define a label, each uses its own.

  • a675a48: A redirect from /index to / no longer breaks the home page’s Markdown copy. A redirect that moves a page also redirects its .md and .mdx copies, and /index.md is the home page’s own copy, so that redirect turned /index.md into a redirect to itself: every redirect file looped on it, and a static build replaced the copy with a redirect page, while build, validate, and audit all passed. A redirect now never takes over a Markdown copy Blume serves. One you write from a copy yourself, like /guide.md or /index.md, warns BLUME_REDIRECT_MATCHES_PAGE, since agents lose that copy.

  • ed17ce5: BLUME_NAV_INDEX_TITLE_MISMATCH no longer says the page’s <title> shows its frontmatter title when seo.title replaces it, and its fix no longer suggests leaving an intentional difference alone, which blume validate --strict fails on. It now names the ways to clear it: match the page’s title and the folder’s meta.ts title, set the hidden index page’s sidebar.label to the folder title to keep a different heading on purpose, or show the index row again. A hidden index page whose sidebar.label matches its folder’s title no longer warns.

  • a01a348: With lastModified: "git", renaming a page without changing its content, like page.md to page.mdx or a move to another folder under the content root, no longer resets its “Last updated” date to the rename. Blume now follows renames, as git log --follow does, and dates the page from the last commit that changed it. It still reads the dates with one git log per build. A rename that also edits the file dates the page at that commit.

  • 87dc7dd: llms.txt and the generated site skill now summarize each page with its meta description, the same text as the page’s <meta name="description">: seo.description when the page sets one, else description. Before, they read only description, so a page summarized only in seo.description, like a generated reference page or an OpenAPI operation page, was listed with no summary.

  • 07a34bc: Under the group sidebar display, a group that is the only row at the top of the sidebar now starts open. A folder that wraps every page started collapsed on any page outside it, so a home page outside the folder showed a sidebar of one closed row. A group whose meta sets collapsed: true still starts closed.

  • 560e316: blume dev, blume build, and blume check warn about a ::: directive in a .md page. Directives render only in .mdx, so a :::note callout in a .md page showed its ::: lines as text with no warning. BLUME_MD_DIRECTIVE names the file and line and says to rename the page to .mdx, or, for a name that isn’t a callout type, lists the callout types. Code blocks and inline code are skipped.

  • 08eda5d: An HTML comment in a .md partial no longer shows on an .mdx page that includes it. The partial is read as MDX there, which has no HTML comments, so <!-- a note --> rendered as visible text (with its dashes turned into en dashes). Its comments are now spliced as MDX comments ({/* a note */}), so they stay hidden, while a comment shown in a code block or inline code stays as written.

  • 2efc0b4: blume dev, blume build, and blume check warn about two things in .mdx that used to pass blume check and then break the page:

    • BLUME_MDX_ATTRIBUTE_LIST: an attribute list like { width="300" } or {: .note } after an image or paragraph. MDX reads the braces as JavaScript, so the build failed at render time with a bare ReferenceError that named only the route. The warning names the file and line and suggests setting the attributes on a JSX element instead.
    • BLUME_MDX_UNCLOSED_ELEMENT: an HTML void element without a closing slash, like <img …> or <br>. In an .mdx page it fails to compile, and in a .md partial that an .mdx page includes it silently took in the rest of the partial as its children. The warning points at the partial’s own line.
  • 13f8e67: blume dev, blume build, and blume check warn about two kinds of JSX in .mdx that built green and did nothing:

    • BLUME_MDX_EVENT_HANDLER: a JavaScript event handler on an HTML element, like <button onClick={() => open()}>. The page is static HTML, so the handler never ran. The warning names the file and line and points to islands, which do run in the browser.
    • BLUME_UNKNOWN_PROP: a built-in component with no children given props it doesn’t take, like VitePress’s <Badge type="tip" text="beta" />, which rendered an empty badge. The warning lists the props the component takes and suggests writing the content between the tags. A component you replace in components.ts isn’t checked.
  • e8b4e3f: mdxRemote({ url, files }) no longer warns BLUME_MISSING_SECRET for GITHUB_TOKEN when the url is on raw.githubusercontent.com. A public repository’s raw files need no token, so the warning fired on every build of a site that needed nothing set. Only the github form, which lists files through GitHub’s rate-limited API, still declares the variable. When GitHub refuses a remote file with a 401, 403, or 404 while GITHUB_TOKEN is unset, the warning for the skipped file now says so.

    A remote page whose file opens with a # Heading no longer shows two <h1>s. When that first heading matches the page’s front matter title, or the front matter sets no title, Blume drops it and uses its text as the title. A first heading that differs from title stays.

  • 05a1d47: A build that fails in an .mdx page now says where. A {…} expression that reads a name nothing defines ({user.name}, a {{name}} on a site without that variable, an attribute list like { width="300" }) failed with a bare ReferenceError that named only the route; it’s now BLUME_MDX_UNDEFINED_NAME, at the expression’s file, line, and column, with how to show the braces as text. A compile error in a partial an .mdx page includes named only the page, with no line; it’s now reported at the partial’s own line.

  • 05a1d47: An .mdx page that MDX can’t parse (an HTML comment, an element left open, a stray {) used to pass blume validate, blume doctor, and blume check, and only failed blume build once Astro compiled it. Blume now parses every .mdx page when it reads the project, so all of them report it as BLUME_MDX_SYNTAX, an error at the line and column where MDX stopped, with how to fix it, and blume build stops before it compiles anything. blume build --no-strict now leaves such a page out and builds the rest of the site, as it does a page with invalid front matter; before, the build still failed on it. blume dev keeps the page, so opening it shows the error. A partial that only breaks once an .mdx page includes it is a BLUME_MDX_SYNTAX warning at the partial’s line.

  • fdb7115: The blume-migrate skill’s Mintlify codemod keeps the brand icons Blume renders. It dropped every brand icon as having no Lucide equivalent, but Lucide still ships facebook, github, gitlab, instagram, linkedin, slack, twitter, and youtube, so an icon: github page lost an icon that would have rendered. The codemod now keeps those under the same name and still drops the brands Lucide lacks (discord, x-twitter, docker, and the like, plus apple, whose Lucide icon is the fruit), and the skill’s Mintlify reference says the same.

  • bb126b6: A page whose file git ignores no longer shows an Edit on GitHub link. Generated pages, like a TypeDoc reference written into a gitignored folder under the content root, are never committed, so the link opened a GitHub 404. Blume asks git which page files it ignores with one git check-ignore call per build and drops the link from those pages. A file that a .gitignore rule matches but git already tracks keeps its link, and outside a git repository every page keeps it, as before.

  • e83d42f: Open Graph cards render again for a font whose family name has a word starting with a digit, like the curated source-sans-3 (Source Sans 3) and source-serif-4 (Source Serif 4). The card handed the family name to the renderer unquoted, which reads it as CSS and rejects a word like 3, so setting one of those fonts in theme.fonts failed the build whenever cards rendered. The card now quotes the name, and you no longer need to set seo.og.fonts to work around it.

  • 8fbb6d3: Try it and the generated code samples in an OpenAPI reference now send an Accept header naming the first JSON media type among the operation’s responses (a success response’s first). Before, they sent none, and frameworks like Laravel treat such a request as a browser’s: on a stock Laravel API, sending a request without a token returned a 500 (“Route [login] not defined.”) instead of the documented 401. An Accept header parameter the operation declares still takes precedence.

  • ca9dc01: blume dev, blume build, and blume validate now warn when two operations in an OpenAPI spec share an operationId. Blume keeps both pages by adding the method to the second one’s id (and to its URL when the two share a tag), which used to happen without a sign. BLUME_OPENAPI_DUPLICATE_OPERATION_ID names both operations and where the second one ended up.

  • 223ad49: Request samples and the Try it prefill in an OpenAPI reference now leave out readOnly properties wherever the example comes from. A model-level example, which TypeSpec writes, was copied as written, so every request sample sent the model’s server-generated id. Response examples likewise leave out writeOnly properties. The Try it form no longer lists readOnly fields either, matching the request body’s schema table, which already hid them.

  • d68591a: blume dev, blume build, and blume validate now warn when an OpenAPI spec’s server is a local address (localhost, 127.0.0.1, 0.0.0.0, or [::1]), which frameworks write when the spec is exported on a dev machine. Blume published it as the server Try it sends requests to and the code samples call, which readers can’t reach. BLUME_OPENAPI_LOCAL_SERVER names the spec and the URL, and suggests listing the public URL in servers or setting it with an overlay.

  • 223ad49: An OpenAPI body or response with several named examples now shows all of them, each labeled by its summary or its key. Before, an operation page showed only the first one, unnamed. A response gets a tab per example after its status (200 · A cat), a webhook payload a tab per example after its media type, and a request body an Examples block under its schema.

  • d68591a: blume dev, blume build, and blume validate now warn when a spec that declares OpenAPI 3.1 or later uses nullable, which 3.1 removed. Blume shows those values as nullable, but validators and SDK generators that follow 3.1 read them as never null. BLUME_OPENAPI_NULLABLE names where, as JSON Pointers into the spec, and suggests type: [string, "null"].

  • 18c8ea9: An OpenAPI operation page’s Markdown copy (<route>.md), llms-full.txt, and the MCP server’s get_page now include the operation’s request body and responses. Before, they carried only the endpoint, so the examples a spec records (rswag’s, say) and the request bodies a generator infers (Scramble’s) never reached agents. The copy lists the request body’s media type and top-level properties with its example, every named one included, and each response’s status and description with the examples the spec records, plus a sampled example for the first success response that records none. A webhook’s body is listed as its payload.

  • 223ad49: A property written as a $ref with keywords beside it, which OpenAPI 3.1 allows and ASP.NET Core writes for every enum-typed property, now shows its own description instead of the referenced schema’s. An example, examples, or default beside a $ref is now used in request samples, the Try it prefill, and response examples, where the referenced schema’s value used to win.

  • ca9dc01: An OpenAPI tag’s x-displayName now labels its sidebar group and its section on the overview page. Before, Blume ignored it and showed the raw tag name. The tag’s URL still comes from its name, so adding an x-displayName doesn’t move any page.

  • e1607f1: API operation pages now end like every other docs page: the “Was this page helpful?” rating, a PageFooter component override, and the previous and next links render below the operation, aligned with its description column. Before, operation pages skipped the whole page-end area, so the rating and PageFooter never showed on them.

  • 2efc0b4: blume dev, blume build, and blume check warn about syntax from other docs tools that a page shows as written. Each warning names the file and line, skips code and comments, and says what to write instead:

    • BLUME_TEMPLATE_TAG: a Liquid or Markdoc tag ({% include note.html %}) or a Liquid output ({{ site.title }}) in a .md page. A {{name}} that could be a Blume variable isn’t reported.
    • BLUME_WIKILINK_UNSUPPORTED: a wiki link ([[Home]], [[Text|Page]]) that names one of the site’s pages, or any with a |, with the Markdown link that replaces it. Obsidian vault sources still turn wiki links into links.
    • BLUME_MDC_SYNTAX: a Nuxt Content (MDC) block component (::callout … ::) or inline component (:badge[New]{color="primary"}).
    • BLUME_MD_ATTRIBUTE_LIST: a kramdown or MkDocs attribute list in a .md page, like {: .note } or {: #intro } on its own line or after a block, or { width="300" } after an image. A heading’s {#id} marker is left to BLUME_MD_CURLY_ANCHOR.
  • aece109: A pinned heading id now keeps --, ---, ..., and quotes as written. Smart punctuation, which turns -- into a dash and curls quotes in a heading’s text, also rewrote the id in its [#…] or {#…} marker, so ## Read and write [#read--write] anchored as #read–write (with an en dash): links to #read--write missed the heading, and blume validate reported them as BLUME_BROKEN_ANCHOR. The id is now used exactly as written, on a page and in a partial it includes, so the anchor of such a heading changes from the dashed or curled form to the one in its marker.

  • a56ff73: blume preview now answers a static build’s redirects the way the host files the build writes do. Pattern redirects like /old/* → /new/:splat used to 404, because only the exact redirects had redirect pages in dist/. Every redirect, exact or pattern, now gets its configured status, including the Markdown copies a moved page takes with it.

    blume dev and blume preview also handle trailing slashes the way static hosts do. A slashed page URL like /guide/ used to 404 in both. It now redirects to /guide and keeps the query string. A folder of HTML shipped in public/, like public/demo/index.html, is served at /demo/, and /demo redirects there. Before, blume preview served it only at /demo, so the relative links inside it broke, and blume dev served it at neither URL.

  • e10a457: blume preview serves a page again when a redirect points its old .html URL at it, like /guide.html to /guide after a migration from VitePress, VuePress, or MkDocs. The redirect page for /guide.html lands in a guide.html/ folder, and the preview server took that folder for the page, so /guide showed the redirect page and refreshed to itself forever. A redirect from an .html URL also no longer adds redirects for /guide.html.md and /guide.html.mdx, URLs that never had a Markdown copy.

  • cfdf7a0: Copying a <Prompt> (or opening it in Cursor) keeps the punctuation its body was written with. Smart punctuation curled the body’s quotes and joined its dashes and dots like the rest of the page’s prose, so lang="ts" copied as lang=“ts” and --force as –force, which broke code in the prompt. Quotes, dashes, and ellipses the body’s author typed as such still copy as typed.

  • d68591a: blume dev, blume build, and blume validate now warn when a project ships public/openapi.json. That file takes over /openapi.json, where Blume publishes the OpenAPI description of the site’s JSON docs API, so the description wasn’t generated, while /.well-known/api-catalog still listed /openapi.json as that API’s description. BLUME_PUBLIC_OPENAPI_JSON names the file and suggests moving it to another path, like public/specs/openapi.json, or setting agents.api: false.

  • 5a1dfb9: Under a deployment.base, a root-relative URL in raw HTML now gains the base like a Markdown link does. <a href="/guide">, <img src="/logo.png">, and any other element’s href or src, as raw HTML in .md or an element in .mdx, shipped without the base, so they pointed outside the site, while blume validate passed them. A URL that already starts with the base, an external URL, and a #fragment are left as written, and a raw href still gains no basePath.

  • f2c66a9: A redirect from a page’s own index.html URL, like { from: "/guide/index.html", to: "/guide" }, no longer fails a static build with EISDIR. Astro wrote the redirect page to guide/index.html/index.html, which needs the page’s own guide/index.html file to be a folder. The build now writes no redirect page there, since the page itself is served at that URL. The redirect still goes into _redirects and vercel.json, so hosts that read them answer it with the redirect, and blume dev answers it too.

  • ec6b278: A link to a Markdown file renamed from .md to .mdx, or back, now lands on that file’s page even when the file has a slug or an ordering prefix. A link to ./01-setup.md after the file became 01-setup.mdx, or to ./setup.md when setup.mdx sets its own slug, fell back to a relative link with the extension dropped, which no page serves, so the built link 404’d and blume validate reported BLUME_BROKEN_LINK. Blume now tries the same file with the other extension before that fallback, in the built page and in blume validate alike.

  • 6751fbd: A root-relative link to a Markdown file, like [Setup](/guides/setup.md) (the way VitePress, Docsify, and TypeDoc write one), now lands on the page that file publishes, its slug and ordering prefix included, as a relative ./setup.md link already did. It kept its path, which is also the URL of the page’s Markdown copy, so readers landed on raw Markdown, and blume validate passed it. The link is read from the content root; under a deployment.base, one that starts with the base is read as including it. A link that names no file in your content keeps its path, so a link to a page’s Markdown copy still works, and a raw <a href> keeps its path too. The page’s Markdown copy and llms-full.txt get the same rewrite, and blume validate checks the link at that page.

  • e061f74: A tab at path: "/" on a site with no root index.mdx now links to the first page in the sidebar, as tabs for other sections already did, instead of to /, which has no page. On an archived version’s pages, the root tab still links back to the current docs, now under a basePath too, where it linked to the archived version’s first page. BLUME_NAV_MISSING_PAGE now checks a tab where it links: its href when it has one, and its path otherwise. Before, it checked only path, so a root tab warned on every build, even with href set to a real page.

  • 480d351: A script() analytics adapter with a root-relative src, like script({ src: "/js/redirects.js" }) for a file in public/, now loads it under deployment.base. Before, the tag kept the path as written, so on a site served under a base the script 404ed unless the base was written into src by hand. A src that already starts with the base is left as written, and absolute and protocol-relative URLs pass through unchanged.

  • 6b58575: Blume now requires sharp 0.35.5, which patches a memory vulnerability in its bundled librsvg (GHSA-wq5f-xc86-pv6w). The bug can lead to remote code execution on glibc-based Linux when sharp renders an SVG.

  • f28a742: A colocated SVG whose size Astro can’t read no longer fails the whole build. Astro reads an SVG’s size from its <svg> tag, and only when that tag ends within the file’s first 1,000 bytes with a width and height or a viewBox; a draw.io export puts its whole diagram in a content attribute on the tag, so one diagram failed blume build with NoImageMetadata. The page now shows that SVG as it is, served from /blume-assets/content/… like the copies agents read, and blume dev, blume build, and blume validate warn about it as BLUME_SVG_UNOPTIMIZED at the line that embeds it.

  • 1ba74be: A (group) folder inside a tab section now keeps the order its meta.ts pages sets. The folder adds no URL segment, so it shares the tab’s path, and Blume listed its loose pages above its subgroups as if it were the section’s top level, even when every subgroup was a collapsible group or a page drill-in. Only the section’s own top level lists its loose pages first now, as the folder meta docs describe.

  • 1ca5a00: blume dev, blume build, and blume check warn when theme.accent, theme.action, theme.background, or a seo.og.palette color isn’t a CSS color, like "deep purple" or a hex value without its #. Before, the config took any string and the site shipped it, so browsers ignored every style that used the color, with no warning. BLUME_THEME_COLOR_INVALID names the field, its value, and the line in blume.config.ts that sets it, and lists the accent presets and the color forms to use instead. It’s a warning, so an existing build still passes unless it runs with --strict.

  • 05a1d47: A project tsconfig.json whose extends or references doesn’t resolve, such as a package that isn’t installed or a framework’s generated tsconfig that doesn’t exist yet (Nuxt’s .nuxt/tsconfig.app.json), failed blume build with Astro’s GenerateContentTypesError and no hint that the build reads that file. blume build, blume dev, and blume check now report BLUME_TSCONFIG_EXTENDS at the line that names the target, and say to install, restore, or create it, or remove it from extends or references.

  • 563da2d: BLUME_UNKNOWN_COMPONENT now names every page that uses an unknown component, with a count, instead of only the first one it found. A <Widget> on five pages used to report one route, so fixing that page brought the same warning back for the next.

  • 97fe5db: A page without a frontmatter title that opens with a # Heading shows that heading once. The page took its title from the heading and rendered it as the page heading, but the heading stayed in the body too, so it appeared twice, one above the other, and blume audit reported two <h1> tags. The opening heading now gives the page its title and leaves the body, and the page heading takes its anchor, so links to #the-heading still land. llms-full.txt opens each page’s section with its title, so a body that opens with a # Heading of the same text showed it twice there too; the section now drops the body’s copy. A page with a title, a # heading after other content, and a page in custom or frame mode, which shows no title, keep their headings as before.

  • 2849116: blume validate now says which page an .html link means. Blume serves pages at their routes, never at .html URLs, so a link like ./setup.html or /guides/setup.html 404s unless public/ has that file. validate reported it only as an asset missing from public/ (BLUME_BROKEN_ASSET), and skipped it entirely when the project had no public/ folder. The warning now names the page route to link instead, like /guides/setup for setup.html or /guides for guides/index.html.

  • a9ec1fd: blume validate now checks the src of <img>, <source>, <video>, and <audio> elements, in raw HTML in .md pages and as elements in .mdx pages. Before, a broken src passed validate --strict and 404’d on the built site. A src ships exactly as written, so it’s checked where the browser requests it: ./diagram.png on /guides/setup must be served at /guides/diagram.png, from public/. A relative src that names a file beside the page is reported too, since nothing publishes that file; the BLUME_BROKEN_ASSET warning suggests Markdown image syntax (![alt](./diagram.png)), which does, or a public/ path.

  • d2dda96: blume validate now warns about a link that uses one of ReadMe’s link schemes: doc:, ref:, page:, changelog:, or blog:. Only ReadMe resolves them, so anywhere else each ships as a dead link, and validate passed them. BLUME_UNSUPPORTED_LINK_SCHEME names the file and line; link the page by its path instead. Other schemes, like mailto:, tel:, or an app’s vscode: deep link, are still left alone.

  • 6a524b1: blume validate now reads link syntax the way the page renders it. A link destination in angle brackets, which Prettier writes for a URL with parentheses ([x](<https://example.com/a(b>)), was misread as a broken relative path (BLUME_BROKEN_LINK), and an image path with backslash-escaped parentheses (![](image%20\(115\).png)) was reported missing (BLUME_BROKEN_ASSET) though the build found it. Both are now read without the brackets and escapes. Links inside comments, which never render, are no longer checked: an HTML comment in a .md page, or a {/* … */} comment in an .mdx page. A code fence inside a block quote (> ```ts) now counts as code, so a quoted line in it like > [key: string]: string; is no longer read as a link definition. A line whose < never closes, like [1]: <src/x.ts - function x(, isn’t a link definition either, and when a label is defined twice, only the first definition, the one the page uses, is checked. A fragment link to an <a name="…"> now resolves, like one to an element’s id, instead of warning BLUME_BROKEN_ANCHOR.

  • 40fbae3: blume validate now checks where each redirect leads. Only blume audit did, on the built site, so a redirect to a page that was never written passed validate --strict and 404’d once deployed. An exact internal to must now be a page, a file in public/ or one Blume generates, or another redirect’s from; for a pattern, something must be served under the literal part of to before its first capture (/v2/ in /v2/:slug*). One that leads nowhere warns BLUME_BROKEN_REDIRECT, located at its to in blume.config.ts. Links to a redirect’s from stay valid.

    validate also accepts links to API reference pages, like a scalar() page at /reference, which it reported as BLUME_BROKEN_LINK though the build serves them.

  • a079099: blume validate now accepts links to the files Blume writes beside your pages. A link to /llms.txt, /llms-full.txt, /sitemap.xml, /robots.txt, an RSS feed, /openapi.json, /skill.md, or a .well-known file like /.well-known/agent-skills/index.json was reported as a missing asset (BLUME_BROKEN_ASSET), which failed --strict. Each now counts while the config has its feature on, so a link to /llms.txt with agents.llmsTxt off is still reported. A link to a public/ folder with an index.html, like /demo or /demo/ for public/demo/index.html, now resolves too, instead of failing as BLUME_BROKEN_LINK.

    A project with no public/ folder no longer skips its asset checks with a BLUME_ASSETS_UNCHECKED note. It ships no public files, so a link to /logo.png there is reported as BLUME_BROKEN_ASSET, and images beside a page are checked as before.

    A navigation.featured link, header action, or call to action that points at a public/ file (/spec.pdf) or a generated one (/llms.txt) no longer warns BLUME_NAV_MISSING_PAGE in blume dev, blume build, and blume doctor.

  • 47865d3: A vercel() server build that fails its function-bundle check now removes .vercel/output/config.json. The adapter had already written that file, but Blume’s pattern redirects, Accept: text/markdown routes, and base routes were never added. The output looked deployable, and vercel deploy --prebuilt would have shipped it without them. The same happens when the build can’t move the routes under deployment.base. The function bundles stay in place so you can inspect them.

  • 07ac505: <YouTube> embeds a playlist URL as the playlist. A url like https://www.youtube.com/embed/videoseries?list=… used to embed a video with the id videoseries, which doesn’t exist, and https://www.youtube.com/playlist?list=… rendered nothing. Both now embed the playlist, the page’s Markdown copy links to it, and a playlist pasted into a Notion video block becomes the embed too.

Last updated on

Was this page helpful?