blume@2.2.0
Minor Changes
- 3ba8dc7:
blume migratenow 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 bundledblume-migrateskill 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.mjsmaps each OpenAPI endpoint to its Blume route for redirects,pin-heading-ids.mjskeeps a migrated site’s old heading anchors working, andinclude-excerpts.mjsgenerates 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
undicito send remote API spec fetches throughHTTP_PROXYorHTTPS_PROXYwhen either is set. That dependency is now undici 8, which requires Node.js 22.19 or newer.blume doctorwarns when the running Node.js is older than that. - be943aa: Add a
<Changelog />component, so a hand-written/changelogpage can put an introduction or a feed link above the generated release list instead of rebuilding the list by hand. Place the tag inchangelog/index.mdxwithmode: centerto 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, MCPget_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’sinfo.descriptionis often a document of its own under# Introductionand# Authenticationheadings, so the overview page had several<h1>s besides its title. Headings ininfo.descriptionand 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
summaryis 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, soGET /petsandPOST /petsshared one, and an unedited spec failedblume validate --strictwithBLUME_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
listoperation 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.mdand.mdxcopies) 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 agraphql()reference, a type page whose name matches a root field moves the same way (/graphql/objects/pet-objectto/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.tsin a tag’s folder that setspagesstill 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
InboxesThreadsis served at/reference/inboxes-threads/…instead of/reference/inboxesthreads/…. The old URLs keep working: Blume redirects each one (and its.mdand.mdxcopies) to the page’s new URL with a 301, unless a page or a redirect you configured is already at that URL. This applies toopenapi(),asyncapi()(including untagged operations grouped by channel address), andgraphql()references. -
6db6cdd:
blume audit --onlyand--skipnow narrow the counts the report prints to the checks they leave in: the audit count in the summary, theauditscount in--json, and the count beside each skipped tier. Before,blume audit --only redirectsfiltered 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 auditnow 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/mcpto/user-api, and every host gets that rule. When/user-apiisn’t a page, the audit now reportsBLUME_AUDIT_REDIRECT_BROKENfor the pattern and names/mcp. A pattern that sends its bare path to itself, like/a/*→/a, is reported asBLUME_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.tsunder a content root outside the project folder, likecontent: { root: "../docs" }or a sibling package in a monorepo, can nowimport { defineMeta } from "blume". Before, Blume looked forblumefrom the meta file’s own folder upward. A project that installsblumein its ownnode_modulesfailed every such file withBLUME_META_LOAD_FAILED: Cannot find module 'blume'. Blume now resolvesblumeand its subpaths (blume/sources,blume/schema, …) to the running Blume package wherever the file lives. The same fix applies to files thatblume.config.tsimports from outside the project. -
000f0ac:
blume validatenow 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. TheBLUME_BROKEN_LINKfix now names the file as a partial and suggests splicing it in with<include>, renaming it, or adding"!**/_*"tocontent.exclude. -
c2e2a02:
<Card img="./cover.png">now finds an image next to the page, likedoes. The card renderedimgas written, so the browser resolved a relative path against the page’s URL and the image 404’d, whileblume validatenever checked it. A card’s relativeimgis 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.mdxcopy agents read points at the same URL.blume validatechecksimglike any image embed and reports a missing file asBLUME_BROKEN_ASSET. -
07ac505: A
<CodeBlock>with nocodeprop, like a fenced block wrapped in<CodeBlock>…</CodeBlock>the way Fern and Mintlify write code, no longer fails the build with a bareTypeErrorthat named no file or line. Withoutcode,CodeBlocknow renders its children as written, so the wrapped fence shows as it would on its own, and the page’s Markdown copy andllms-full.txtunwrap 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-errorand--blume-code-warningtokens (each with a-borderpartner) restyle the lines. -
8b05bef:
blume dev,blume build, andblume checkwarn 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 asBLUME_UNKNOWN_CODE_LANGUAGEwith the file and line. A fence option from another docs tool did nothing without a word: MkDocs’hl_lines="2 3"andlinenums="1",showLineNumbers, Mintlify’slines, Fern’swordWrap, andfilename="…". Each now warns asBLUME_CODE_FENCE_OPTIONand gives the Blume spelling ({2,3},lineNumbers,wrap,title="…"). On a Rust block, rustdoc’s and mdBook’signore,no_run,should_panic,compile_fail,edition2015throughedition2024,noplayground, andeditablewarn 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 showLineNumbersis titledapp.js. -
3f5ae80: A code block’s title is every word after its language. Before, only the first word counted, so
```javascript Install the clientwas titled “Install” and the rest of the line was dropped. Keywords likelineNumbers, line ranges, andkey="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()andgraphql()now warn aboutcodeSamplesids 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_SAMPLEfor GraphQL) names the ids and lists the ones Blume accepts. ReadMe’scplusplusis now accepted as an alias forcpp. Itsobjectivechas no generated sample, so it warns. -
194787d: The copy button on a
```consoleor```shellsessionblock copies only the commands, without their prompts or output. Before, copying$ npm i blumeand 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 afilesystem()source’sexclude) now adds to the default["**/_*", "**/.*"]instead of replacing it. Before, settingexclude: ["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.mdxpublished at/changelog/05-2022andchangelog/2024-01.mdxat/changelog/01, with no warning, and two files that differed only in that number collided on one route. Now12-05-2022(D-M-YYYYorM-D-YYYY) and2024-01(YYYY-MM) stay whole, like an ISO date (2024-01-05) already did, so those pages publish at/changelog/12-05-2022and/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, andblume checkwarn when a:::container’s closing line has text after its colons. Inside a:::warning, a::: cardline closes the callout, socardwas dropped from the page and the text after it fell outside the callout, with no warning.BLUME_DIRECTIVE_CLOSING_TEXTnames 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::: tipfrom VitePress, VuePress, or Docusaurus v2, isn’t a directive and shows as text. Blume now warns about it asBLUME_DIRECTIVE_SPACED_NAMEand gives the unspaced spelling, with a title moved into brackets (:::tip[Title]). -
3f8beb7:
blume dev,blume build, andblume checkwarn when a callout opener has text after its name.:::tip Some titleopens atipcallout but dropsSome title, so the callout rendered untitled with no warning.BLUME_DIRECTIVE_OPENING_TEXTnames the file and line and gives the bracketed spelling that keeps the title,:::tip[Some title]. -
8b18782: A backslash-escaped
<in an.mdxpage no longer counts as a component tag. Text likePromise\<App>or\<Not set>renders as literal text, butblume devandblume buildstill 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 validatelikewise 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:
```JSONand```Dockerfilehighlight like```jsonand```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 (thesrcof an<img>,<video>,<source>, or<audio>, and thehrefof an<a>or a component, as raw HTML in.mdor 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 andllms-full.txttoo. A partial’s link moves with it when the partial is included from another folder.blume validateaccepts these links, and no longer warns that an<img src>naming a file beside the page isn’t published. -
059c83f:
blume dev,blume build, andblume doctorwarn about folder meta that does nothing. Ameta.tspagesentry that names no page or folder in its group was ignored without a word;BLUME_META_UNKNOWN_PAGEnow 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). Ameta.tsoutside everyincludeglob of its content source is never read, which left a reference’s tag-foldermeta.tssilently ignored on a site whoseincludelists only its own folders. When such a file sits in a sidebar group’s folder,BLUME_META_OUTSIDE_INCLUDEnow says so and suggests anincludeglob that reaches it. -
b04f1b7: A
meta.tsin 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 ameta.tsthat 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, formeta.tsandmeta.$.tsalike. -
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.mdpage, which renders no components, the quote stays a quote, andblume dev,blume build, andblume checknow warn about it asBLUME_MD_GITHUB_ALERTso 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>titlenow 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#installinstead of#install-betaand lists as “Install”. A fragment can’t be redirected, so links to the old anchors land at the top of the page;blume validatereports 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’sid(orname) 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.mdheading 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, andblume checknow warn about them asBLUME_MD_CURLY_ANCHOR, naming the file and line and the{#id}or[#id]spelling to use, instead of silently giving the heading an id likesetup--setup. -
5c1974b: Hiding the home page with
sidebar.hidden(orhidden) 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 folderindexpage 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: truefrontmatter shorthand now emits the page’s<meta name="robots" content="noindex">and drops its canonical and structured data, exactly likeseo: { noindex: true }. Before, it only took the page out of the sitemap, soblume auditreported the page as indexable but missing from the sitemap. The top-levelhiddenshorthand now also keeps a changelog entry off the/changelogindex, likesidebar.hidden.Hiding a folder’s
indexpage withsidebar.hidden(orhidden) 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
,, orkept 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 validatereads 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.mdand.mdxalike. 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
/indexto/no longer breaks the home page’s Markdown copy. A redirect that moves a page also redirects its.mdand.mdxcopies, and/index.mdis the home page’s own copy, so that redirect turned/index.mdinto a redirect to itself: every redirect file looped on it, and a static build replaced the copy with a redirect page, whilebuild,validate, andauditall passed. A redirect now never takes over a Markdown copy Blume serves. One you write from a copy yourself, like/guide.mdor/index.md, warnsBLUME_REDIRECT_MATCHES_PAGE, since agents lose that copy. -
ed17ce5:
BLUME_NAV_INDEX_TITLE_MISMATCHno longer says the page’s<title>shows its frontmattertitlewhenseo.titlereplaces it, and its fix no longer suggests leaving an intentional difference alone, whichblume validate --strictfails on. It now names the ways to clear it: match the page’s title and the folder’smeta.tstitle, set the hidden index page’ssidebar.labelto the folder title to keep a different heading on purpose, or show the index row again. A hidden index page whosesidebar.labelmatches its folder’s title no longer warns. -
a01a348: With
lastModified: "git", renaming a page without changing its content, likepage.mdtopage.mdxor a move to another folder under the content root, no longer resets its “Last updated” date to the rename. Blume now follows renames, asgit log --followdoes, and dates the page from the last commit that changed it. It still reads the dates with onegit logper build. A rename that also edits the file dates the page at that commit. -
87dc7dd:
llms.txtand the generated site skill now summarize each page with its meta description, the same text as the page’s<meta name="description">:seo.descriptionwhen the page sets one, elsedescription. Before, they read onlydescription, so a page summarized only inseo.description, like a generated reference page or an OpenAPI operation page, was listed with no summary. -
07a34bc: Under the
groupsidebar 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 setscollapsed: truestill starts closed. -
560e316:
blume dev,blume build, andblume checkwarn about a:::directive in a.mdpage. Directives render only in.mdx, so a:::notecallout in a.mdpage showed its:::lines as text with no warning.BLUME_MD_DIRECTIVEnames 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
.mdpartial no longer shows on an.mdxpage 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, andblume checkwarn about two things in.mdxthat used to passblume checkand 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 bareReferenceErrorthat 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.mdxpage it fails to compile, and in a.mdpartial that an.mdxpage 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, andblume checkwarn about two kinds of JSX in.mdxthat 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 incomponents.tsisn’t checked.
-
e8b4e3f:
mdxRemote({ url, files })no longer warnsBLUME_MISSING_SECRETforGITHUB_TOKENwhen theurlis onraw.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 thegithubform, 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 whileGITHUB_TOKENis unset, the warning for the skipped file now says so.A remote page whose file opens with a
# Headingno longer shows two<h1>s. When that first heading matches the page’s front mattertitle, or the front matter sets notitle, Blume drops it and uses its text as the title. A first heading that differs fromtitlestays. -
05a1d47: A build that fails in an
.mdxpage 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 bareReferenceErrorthat named only the route; it’s nowBLUME_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.mdxpage includes named only the page, with no line; it’s now reported at the partial’s own line. -
05a1d47: An
.mdxpage that MDX can’t parse (an HTML comment, an element left open, a stray{) used to passblume validate,blume doctor, andblume check, and only failedblume buildonce Astro compiled it. Blume now parses every.mdxpage when it reads the project, so all of them report it asBLUME_MDX_SYNTAX, an error at the line and column where MDX stopped, with how to fix it, andblume buildstops before it compiles anything.blume build --no-strictnow 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 devkeeps the page, so opening it shows the error. A partial that only breaks once an.mdxpage includes it is aBLUME_MDX_SYNTAXwarning at the partial’s line. -
fdb7115: The
blume-migrateskill’s Mintlify codemod keeps the brand icons Blume renders. It dropped every brand icon as having no Lucide equivalent, but Lucide still shipsfacebook,github,gitlab,instagram,linkedin,slack,twitter, andyoutube, so anicon: githubpage 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, plusapple, 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-ignorecall per build and drops the link from those pages. A file that a.gitignorerule 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) andsource-serif-4(Source Serif 4). The card handed the family name to the renderer unquoted, which reads it as CSS and rejects a word like3, so setting one of those fonts intheme.fontsfailed the build whenever cards rendered. The card now quotes the name, and you no longer need to setseo.og.fontsto work around it. -
8fbb6d3: Try it and the generated code samples in an OpenAPI reference now send an
Acceptheader 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. AnAcceptheader parameter the operation declares still takes precedence. -
ca9dc01:
blume dev,blume build, andblume validatenow warn when two operations in an OpenAPI spec share anoperationId. 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_IDnames both operations and where the second one ended up. -
223ad49: Request samples and the Try it prefill in an OpenAPI reference now leave out
readOnlyproperties wherever the example comes from. A model-levelexample, which TypeSpec writes, was copied as written, so every request sample sent the model’s server-generatedid. Response examples likewise leave outwriteOnlyproperties. The Try it form no longer listsreadOnlyfields either, matching the request body’s schema table, which already hid them. -
d68591a:
blume dev,blume build, andblume validatenow 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_SERVERnames the spec and the URL, and suggests listing the public URL inserversor setting it with an overlay. -
223ad49: An OpenAPI body or response with several named
examplesnow shows all of them, each labeled by itssummaryor 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, andblume validatenow warn when a spec that declares OpenAPI 3.1 or later usesnullable, 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_NULLABLEnames where, as JSON Pointers into the spec, and suggeststype: [string, "null"]. -
18c8ea9: An OpenAPI operation page’s Markdown copy (
<route>.md),llms-full.txt, and the MCP server’sget_pagenow 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
$refwith keywords beside it, which OpenAPI 3.1 allows and ASP.NET Core writes for every enum-typed property, now shows its owndescriptioninstead of the referenced schema’s. Anexample,examples, ordefaultbeside a$refis 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-displayNamenow 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 anx-displayNamedoesn’t move any page. -
e1607f1: API operation pages now end like every other docs page: the “Was this page helpful?” rating, a
PageFootercomponent 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 andPageFooternever showed on them. -
2efc0b4:
blume dev,blume build, andblume checkwarn 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.mdpage. 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.mdpage, 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 toBLUME_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--writemissed the heading, andblume validatereported them asBLUME_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 previewnow answers a static build’s redirects the way the host files the build writes do. Pattern redirects like/old/*→/new/:splatused to 404, because only the exact redirects had redirect pages indist/. Every redirect, exact or pattern, now gets its configured status, including the Markdown copies a moved page takes with it.blume devandblume previewalso handle trailing slashes the way static hosts do. A slashed page URL like/guide/used to 404 in both. It now redirects to/guideand keeps the query string. A folder of HTML shipped inpublic/, likepublic/demo/index.html, is served at/demo/, and/demoredirects there. Before,blume previewserved it only at/demo, so the relative links inside it broke, andblume devserved it at neither URL. -
e10a457:
blume previewserves a page again when a redirect points its old.htmlURL at it, like/guide.htmlto/guideafter a migration from VitePress, VuePress, or MkDocs. The redirect page for/guide.htmllands in aguide.html/folder, and the preview server took that folder for the page, so/guideshowed the redirect page and refreshed to itself forever. A redirect from an.htmlURL also no longer adds redirects for/guide.html.mdand/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, solang="ts"copied aslang=“ts”and--forceas–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, andblume validatenow warn when a project shipspublic/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-catalogstill listed/openapi.jsonas that API’s description.BLUME_PUBLIC_OPENAPI_JSONnames the file and suggests moving it to another path, likepublic/specs/openapi.json, or settingagents.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’shreforsrc, as raw HTML in.mdor an element in.mdx, shipped without the base, so they pointed outside the site, whileblume validatepassed them. A URL that already starts with the base, an external URL, and a#fragmentare left as written, and a rawhrefstill gains nobasePath. -
f2c66a9: A redirect from a page’s own
index.htmlURL, like{ from: "/guide/index.html", to: "/guide" }, no longer fails a static build withEISDIR. Astro wrote the redirect page toguide/index.html/index.html, which needs the page’s ownguide/index.htmlfile 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_redirectsandvercel.json, so hosts that read them answer it with the redirect, andblume devanswers it too. -
ec6b278: A link to a Markdown file renamed from
.mdto.mdx, or back, now lands on that file’s page even when the file has aslugor an ordering prefix. A link to./01-setup.mdafter the file became01-setup.mdx, or to./setup.mdwhensetup.mdxsets its ownslug, fell back to a relative link with the extension dropped, which no page serves, so the built link 404’d andblume validatereportedBLUME_BROKEN_LINK. Blume now tries the same file with the other extension before that fallback, in the built page and inblume validatealike. -
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, itsslugand ordering prefix included, as a relative./setup.mdlink already did. It kept its path, which is also the URL of the page’s Markdown copy, so readers landed on raw Markdown, andblume validatepassed it. The link is read from the content root; under adeployment.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 andllms-full.txtget the same rewrite, andblume validatechecks the link at that page. -
e061f74: A tab at
path: "/"on a site with no rootindex.mdxnow 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 abasePathtoo, where it linked to the archived version’s first page.BLUME_NAV_MISSING_PAGEnow checks a tab where it links: itshrefwhen it has one, and itspathotherwise. Before, it checked onlypath, so a root tab warned on every build, even withhrefset to a real page. -
480d351: A
script()analytics adapter with a root-relativesrc, likescript({ src: "/js/redirects.js" })for a file inpublic/, now loads it underdeployment.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 intosrcby hand. Asrcthat already starts with the base is left as written, and absolute and protocol-relative URLs pass through unchanged. -
6b58575: Blume now requires
sharp0.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 acontentattribute on the tag, so one diagram failedblume buildwithNoImageMetadata. The page now shows that SVG as it is, served from/blume-assets/content/…like the copies agents read, andblume dev,blume build, andblume validatewarn about it asBLUME_SVG_UNOPTIMIZEDat the line that embeds it. -
1ba74be: A
(group)folder inside a tab section now keeps the order itsmeta.tspagessets. 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 collapsiblegroupor apagedrill-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, andblume checkwarn whentheme.accent,theme.action,theme.background, or aseo.og.palettecolor 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_INVALIDnames the field, its value, and the line inblume.config.tsthat 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.jsonwhoseextendsorreferencesdoesn’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), failedblume buildwith Astro’sGenerateContentTypesErrorand no hint that the build reads that file.blume build,blume dev, andblume checknow reportBLUME_TSCONFIG_EXTENDSat the line that names the target, and say to install, restore, or create it, or remove it fromextendsorreferences. -
563da2d:
BLUME_UNKNOWN_COMPONENTnow 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
titlethat opens with a# Headingshows 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, andblume auditreported 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-headingstill land.llms-full.txtopens each page’s section with its title, so a body that opens with a# Headingof the same text showed it twice there too; the section now drops the body’s copy. A page with atitle, a#heading after other content, and a page incustomorframemode, which shows no title, keep their headings as before. -
2849116:
blume validatenow says which page an.htmllink means. Blume serves pages at their routes, never at.htmlURLs, so a link like./setup.htmlor/guides/setup.html404s unlesspublic/has that file.validatereported it only as an asset missing frompublic/(BLUME_BROKEN_ASSET), and skipped it entirely when the project had nopublic/folder. The warning now names the page route to link instead, like/guides/setupforsetup.htmlor/guidesforguides/index.html. -
a9ec1fd:
blume validatenow checks thesrcof<img>,<source>,<video>, and<audio>elements, in raw HTML in.mdpages and as elements in.mdxpages. Before, a brokensrcpassedvalidate --strictand 404’d on the built site. Asrcships exactly as written, so it’s checked where the browser requests it:./diagram.pngon/guides/setupmust be served at/guides/diagram.png, frompublic/. A relativesrcthat names a file beside the page is reported too, since nothing publishes that file; theBLUME_BROKEN_ASSETwarning suggests Markdown image syntax (), which does, or apublic/path. -
d2dda96:
blume validatenow warns about a link that uses one of ReadMe’s link schemes:doc:,ref:,page:,changelog:, orblog:. Only ReadMe resolves them, so anywhere else each ships as a dead link, andvalidatepassed them.BLUME_UNSUPPORTED_LINK_SCHEMEnames the file and line; link the page by its path instead. Other schemes, likemailto:,tel:, or an app’svscode:deep link, are still left alone. -
6a524b1:
blume validatenow 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 (.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.mdpage, or a{/* … */}comment in an.mdxpage. 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’sid, instead of warningBLUME_BROKEN_ANCHOR. -
40fbae3:
blume validatenow checks where each redirect leads. Onlyblume auditdid, on the built site, so a redirect to a page that was never written passedvalidate --strictand 404’d once deployed. An exact internaltomust now be a page, a file inpublic/or one Blume generates, or another redirect’sfrom; for a pattern, something must be served under the literal part oftobefore its first capture (/v2/in/v2/:slug*). One that leads nowhere warnsBLUME_BROKEN_REDIRECT, located at itstoinblume.config.ts. Links to a redirect’sfromstay valid.validatealso accepts links to API reference pages, like ascalar()page at/reference, which it reported asBLUME_BROKEN_LINKthough the build serves them. -
a079099:
blume validatenow 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-knownfile like/.well-known/agent-skills/index.jsonwas 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.txtwithagents.llmsTxtoff is still reported. A link to apublic/folder with anindex.html, like/demoor/demo/forpublic/demo/index.html, now resolves too, instead of failing asBLUME_BROKEN_LINK.A project with no
public/folder no longer skips its asset checks with aBLUME_ASSETS_UNCHECKEDnote. It ships no public files, so a link to/logo.pngthere is reported asBLUME_BROKEN_ASSET, and images beside a page are checked as before.A
navigation.featuredlink, header action, or call to action that points at apublic/file (/spec.pdf) or a generated one (/llms.txt) no longer warnsBLUME_NAV_MISSING_PAGEinblume dev,blume build, andblume 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/markdownroutes, and base routes were never added. The output looked deployable, andvercel deploy --prebuiltwould have shipped it without them. The same happens when the build can’t move the routes underdeployment.base. The function bundles stay in place so you can inspect them. -
07ac505:
<YouTube>embeds a playlist URL as the playlist. Aurllikehttps://www.youtube.com/embed/videoseries?list=…used to embed a video with the idvideoseries, which doesn’t exist, andhttps://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.