blume@2.0.2
Patch Changes
-
e80b141: Only a reader opening an
<AccordionItem>or<Expandable>writes its id to the URL hash. AdefaultOpenitem on the page a client-side navigation lands on no longer replaces the hash the reader arrived with. -
4f9d1bf:
llms.txt,agent-readability.json, the API and AI catalogs, and the_headersandvercel.jsonrules now list the agent skills index only when the build published a skill, and the MCP server only when it was generated, rather than wheneveragents.skillsoragents.mcpis set. The API catalog is now served asapplication/linkset+jsonwith the RFC 9727profileparameter, in itsContent-Typeand in the homepageLinkheader. -
4f9d1bf: Root-relative links in the agent Markdown (
.mdand.mdxmirrors,llms-full.txt, MCPget_page) now match the rendered page:[Install](/guides/install)and<Card href="/guides/config">gain the site’sdeployment.baseandbasePath, and on a translated page they point at that locale’s copy of the page when it exists. A paragraph written straight after a converted<Callout>,<Steps>, or<Prompt>now stays its own paragraph instead of joining the quote or list, and<Visibility>markup inside inline code or an indented code block (in a tab or step) is left as written. -
a70a0f1: GraphQL references build from schemas that use directives they don’t declare, such as Apollo Federation subgraphs (
@key,@link) and AppSync schemas (@aws_*); syntax errors and unknown types still fail the build. A custom scalar’s@specifiedByURL is a link only when it’s a safe web address, so ajavascript:URL from a remote schema shows as text. OpenAPI overview pages list tag sections in the order the spec’stagsdeclares them, code spans in description lines that start with HTML keep their braces, and the build warns that OpenAPI 3.1webhooksaren’t rendered instead of dropping them silently. -
f2ebe6f:
blume init --template apinow writes a small example spec,openapi.json, besideblume.config.tsand pointsopenapi()at it, so a new project’s first build no longer depends on the public Petstore server. Its operation pages carry descriptions that a freshblume auditaccepts. -
591c39e: The assistant shows its error message when an answer comes back empty, which is how a provider failure after the response starts (a bad key, a rate limit, an unknown model) arrives, instead of leaving a pulsing placeholder that never resolves.
useAssistantfromblume/hooksdoes the same. -
4f9d1bf: On a versioned site, the assistant now grounds its answers in the docs version the reader is viewing, the current docs unless they’re on an archived page, instead of retrieving from every archived copy of each page.
-
0befec9:
blume auditpasses on Blume’s own output for multilingual and versioned sites. The hreflang checks and the non-canonical page check skip i18n fallback copies, which canonicalize to the page they copy on purpose; the hreflang checks also accept archived pages that canonicalize to the latest docs, and match non-ASCII slugs. The sitemap and llms.txt checks no longer flag pages those files leave out on purpose: fallback copies, archived pages that canonicalize elsewhere, and pages withai: { exclude: true }, which the llms.txt fix now names. A sitemap index, which Blume writes past 50,000 URLs, is read through its child sitemaps, and the size limits apply to each file.Other checks no longer report what the config asked for. Noindex pages aren’t asked for a canonical, robots.txt isn’t expected to name a sitemap when
seo.sitemapis off or to be reachable whenseo.robotsis off,/_vercel/imageURLs aren’t reported as broken images, and a locale prefix such aspt-BRdoesn’t count as an uppercase URL. -
0befec9: The
blume auditreport labels each check with the number of distinct pages it affects and previews each page once, while--verbosestill lists every finding. A check’s glyph follows its findings’ actual severity, so an external link downgraded to a warning no longer shows as an error. Each distinct fix in a group is printed, and length findings on pages generated from an API spec point at the spec’s summary or description instead of front matter.On a site with
deployment.base, links, assets, andog:imageURLs that leave out the base are reported, since the deployed site doesn’t serve them.blume audit --urlrejects a value with no scheme, such asexample.com, with a message suggesting thehttps://form instead of failing with an internal error. -
e80b141:
<AutoTypeTable>shows the right type for properties inherited from an interface in another file (interface Props extends Base, withBaseimported). Those rows read their type from the file that declares them, where they used to show a slice of unrelated text from the documented file. -
e80b141: A
<Badge>with avariantit doesn’t recognize, such asinfo, renders as the default gray badge instead of failing the page build. -
591c39e: The banner’s link is written as if the site were mounted at root, like a featured link: with
basePathset it lands under the base, and on a multi-locale site it points at the reader’s locale when that locale serves the page. A link to a route served outside the docs (a custom page, the generated changelog) keeps its own path. A<TypeTable>entry’stypeDescriptionLinkgets the base too. -
351d6ab:
blume buildreports a page Astro can’t compile or render (MDX that doesn’t parse, say) as aBLUME_BUILD_FAILEDerror at that file and position, instead of an internal error asking you to report a Blume bug. After avercel()ornetlify()server build,blume previewexplains that the adapter has no local preview server and suggestsblume devor the host’s preview deploy, and in an ejected appblume check,blume sync, andblume previewnow stop likedevandbuilddo, naming the app’s own command to run instead. -
3b5e90a: A
notion()source saves a downloaded asset only when the server reports an image or video type, or when the response names no type and the URL has an image or video extension. An “image” that links to an HTML page keeps its original URL with a warning instead of being served as a page from/blume-assets/, and an image whose caption holds a bracket or whose URL holds spaces or parentheses is now downloaded too, instead of keeping a signed Notion URL that expires./blume-assets/publishes only the images and videos a source downloaded, so a file an earlier build saved under another extension is no longer served, and a downloaded video carries its video type in dev. -
3b5e90a: Content imported from a CMS or GitHub releases can no longer run script on the docs site. A Sanity code span keeps its text as code however many backticks or braces it holds, and a
javascript:link keeps only its label wherever it comes from: a Sanity link, a scheme hidden behind an escape (javascript:,javascript:), a release note’s footnote, or a Contentful, Payload, or Strapi Markdown field. A link’s URL can’t close it early to start a second link, a code block whose language holds a backtick still opens its fence, and an unsupported block’s type can’t end its comment. Lowered rich text also keeps## text, indented text, and animportfollowed by code in the next run as prose, instead of rendering a heading or code block or failing the MDX page. -
3b5e90a: Sanity bold and italic spans keep their edge spaces outside the markers, so
Helloin bold beforeworldrenders as bold text instead of literal asterisks, and nested list items indent to their parent item’s text, so an item under a numbered one nests instead of joining the parent’s line. Notion video captions show<and>as typed rather than as<and>, and a rate-limited Notion request waits for theRetry-Afterthe API sends. -
f2ebe6f: A config error now points at the line that holds the problem: an invalid
redirects[2].statusat the third redirect rather than the first, and a missing key such asgithub.repoori18n.locales[1].labelat its parent object or array entry rather than at a same-named key later in the file, so issues also list in the order they appear. Ablume.config.tswithout a default export (a baredefineConfig({ … })call,export const config, orexport default null) now fails with one diagnostic naming the file and the missingexport default, where it used to build a default site or fail with an unrelated error. ThedefineConfighover docs no longer listmarkdown.mathor inline-highlighting options the config doesn’t accept. -
f2ebe6f: Config paths and URLs are checked up front.
deployment.sitemust be an absolutehttp://orhttps://URL, solocalhost:4321,mailto:, andjavascript:values fail with a hint instead of prefixing every sitemap entry and canonical link. Redirects need a leading slash onfromand on a relativeto, since a path without one never gained the base path.basePathrejects a full URL, query, or fragment. A navigation tab’spathis normalized to one leading slash and no trailing slash, so/guides/marks its tab and scopes the sidebar, andapilinks to/api. -
5f5c72a: With
basePath: "/docs", a content folder nameddocs/now publishes beneath the base (docs/guide.mdat/docs/docs/guide) instead of colliding with the root pages. Foldermeta.tsdiscovery now honors the content source’sincludeandexcludeglobs, so an unrelatedsrc/lib/meta.tsin a.-rooted project is no longer imported, and meta in a lowercase locale folder (pt-br/) now applies to its configured locale (pt-BR). -
abb982a: Only file and folder names lose an ordering prefix (
01-intro.mdx→/intro, and an Obsidian note01 Intro→/intro). A frontmatterslug, a GitHub release tag, and a CMS slug keep their leading numbers, so releases1.0.0and2.0.0publish at/changelog/1-0-0and/changelog/2-0-0instead of colliding on/changelog/0-0. A version (1.2.0.md) or an ISO date (2024-01-05-first-post.md) is part of a name rather than an order, which also stops posts from the same year raising a duplicate sidebar order warning. A sourceprefixwritten with slashes (sanity({ prefix: "/guides" })) now names the sourceguides, so its pages no longer 404 and its asset URLs no longer carry a double slash. -
abb982a: Remote sources keep one snapshot per preview mode and set of options under
.blume/cache/<source>/. A plainblume devafterblume dev --previewno longer serves unpublished drafts, a build whose fetch fails no longer falls back to them,--previewfetches drafts even when a published snapshot is cached, and editing a source’squeryorfieldsrefetches in dev. Contentful’s--previewwithout a Preview API token now fails with a clear error instead of serving the cached snapshot with a warning, and a source passed tocustom()from one of Blume’s engine factories (sanitySource,contentfulSource, …) now honors--previewand caches in the runtime directory like the built-in adapter. -
a6401cf: Add a
databuddy()analytics adapter. It renders Databuddy’s tracker tag withclientIdasdata-client-idand forwards any other option as adata-attribute, and page feedback events reachwindow.databuddy.track. -
f2ebe6f: A
dateFormatthat sets onlytimeZone,calendar, ornumberingSystemkeeps the long date form (July 21, 2026) instead of falling back to a bare numeric date (7/21/2026). SettingdateStyleor a component field such asyearstill picks the shape. -
8d88600:
blume ejectnow addstailwindcssand@tailwindcss/typographyto the project’s dependencies, so the ejected stylesheets resolve under pnpm and other strict linkers, and the ejectedfeatures.tsnamesepub-gen-memoryonly when EPUB export is on. The ejected app also serves colocated images from its raw Markdown at/blume-assets/content/…, and renders the Open Graph card its/changelogpage points at. Withplayground: { proxy: true }on a reference, the ejected app serves the API playground’s built-in proxy at{basePath}/_api-proxy, with the same origin allowlist, instead of every Send returning a 404. -
5f5c72a: English pages (
en,en-GB, and otheren-*codes) on a site whose default locale isn’t English now show Blume’s built-in English interface strings, plus their owni18n.uioverrides, instead of the default locale’s translation. -
351d6ab: Blume no longer takes
NODE_ENVfrom.envfiles, so a monorepo root.envthat sets it for another app can’t turnblume buildinto a development build orblume devinto a production one. Set it in the shell when you need to. -
0befec9: A
severity: warningquestion inblume evalnow warns instead of failing CI, as documented: its miss doesn’t count against the gate or--threshold, so the exit code agrees with the JSON summary’s error count. When an agent CLI exits before reading its prompt,blume evalandblume translatereport the agent’s exit code and stderr for that item instead of aborting the whole run with an internal error. -
aa325a8: A header tab’s
iconnow shows beside its label in the header, on a dropdown tab, and in the mobile navigation drawer. Before,navigation.tabs[].iconwas accepted but never rendered. -
aa325a8: On a multilingual site, a sidebar group with no
meta.tsin a locale now mirrors the fallback locale’s: that folder’smeta.ts(title, order,collapsed,display) and thesidebar.displayits index page sets. Groups set to collapsible no longer render flat on pages that fall back to the default language. -
351d6ab:
blume initinside a pnpm workspace that doesn’t list the new folder underpackagesnow skips the install (pnpm would exit without installing Blume) and tells you to add the folder, instead of saying the project joined the workspace. Thecdline in its next steps is quoted when the directory needs it (cd "my docs"), and wheninit --ejectcan’t eject after an install that ran, it reports the actual error rather than blaming missing dependencies. -
aa325a8: A
<Frame>caption and a<Prompt>description keep code spans (`Array<string>`) and autolinks (<https://…>) intact, while raw HTML in them still shows as text. Before, their<and>rendered as</>in code and as stray brackets around links. -
5f5c72a: Links now resolve the same way in the built site and in
blume validate: relative links on a page namedIndex.mdresolve beside it, as its/…/Indexroute does, and links to dotted page routes such as/releases/v1.2move into the reader’s locale and underbasePath, in content and in componenthrefs alike; only a link no page is served at counts as a public asset.blume versionalso rewrites unprefixed links (/guides/setup) into the snapshot when the default locale keeps its URL prefix, and for pages that exist only in another locale. -
591c39e: More of the reader-facing chrome follows the page’s language: the
<TypeTable>column and row labels,<Component>’s Preview and Code tabs, untitled<Tabs>panels and the tab picker,<Expandable>’s default toggle,<Update>’s default heading,<GithubInfo>’s count labels, the search dialog’s “latest” version tag, and the header on API reference pages. Each has a newi18n.uikey with a translation in every built-in locale. In right-to-left locales the sidebar’s indents and badges, the assistant’s close button, and the arrows on cards, tiles, and the banner link now mirror. -
65edd91: With a
basePath, the_headersandvercel.jsonrules that pincharset=utf-8on raw Markdown now cover every mirror, not only those underbasePath: the section home’s/<basePath>.mdand the generated/index.md,/changelog.md, and/404.mdwent out without a charset, so browsers showed their non-ASCII text as mojibake. -
4f9d1bf: The MCP endpoint now reads a request body only up to 64 KB and answers anything larger with
413. A call to a tool that doesn’t exist, or withargumentsthat aren’t an object, now gets the JSON-RPC Invalid params error (-32602) with a short message. The/mcpoperation inopenapi.jsonnow liststext/event-streambesideapplication/json, so a generated client sends theAcceptheader the endpoint requires. -
5f5c72a: Header and sidebar links now land where they should across versions, locales, and base paths:
- On an archived version’s pages, a tab links into that version’s section (
/v1.0/guides/…) instead of the current docs’ path, which 404ed when the section had no index page. - On a non-default locale, a
/changelogtab opens the changelog timeline instead of the newest entry, and a tab whose path is a custom page opens that page. - With a
basePath, the logo in a non-default locale links inside the base (/docs/fr) instead of to/fr. - Internal
hreflinks in an explicitnavigation.sidebarmove into the reader’s locale, as featured links do. - An explicit
navigation.sidebaritem that can’t render as written now reports a warning: a route that matches no page, arootthat matches no page, or an item with no route,href,root, oritems. - A featured or header link with a query or fragment (
/guides?tab=cli) is no longer reported asBLUME_NAV_MISSING_PAGEwhen its page exists.
- On an archived version’s pages, a tab links into that version’s section (
-
aa325a8: A
netlify()server build now serves the header rules a static build puts in_headers— the homepageLinkheader,charset=utf-8on the raw Markdown and text files, and the.well-knowndiscovery files’ media types and CORS header — by writing them into Netlify’s.netlify/v1/config.json, which a server build reads instead of_headers. -
8d88600: The default 404 page now switches to the reader’s locale under a
deployment.baseorbasePathtoo, and its localized home link includes thebasePath.<Component />preview frames now follow the OS color scheme when browser storage is blocked, instead of rendering without a theme. -
abb982a: Broken-link diagnostics in an Obsidian vault note now name the line in the note itself, rather than a line counted from the frontmatter Blume rewrites when it drops Obsidian-only properties.
-
f2ebe6f: Open Graph cards for non-Latin locales now render their text instead of empty boxes. Each configured locale whose script the card’s built-in font can’t draw adds a Google Noto fallback for it (
Noto Sans JPforja,Noto Sans Devanagariforhi,Noto Sansfor Cyrillic, Greek, and accented Latin, and so on), with no config needed. Latin text keeps the built-in font, so English cards look the same, and an explicitseo.og.fontsstill replaces the whole list. -
a70a0f1: API reference samples now use the examples an OpenAPI 3.0 or Swagger 2.0 spec declares on parameters, request bodies, and responses, which the 3.1 upgrade had moved out of reach, so a path parameter with
example: "pet_123"renders/pets/pet_123instead of/pets/string. Response examples keepreadOnlyfields and leave outwriteOnlyones, and the curl, JavaScript, and Python samples quote header values, URLs, and bodies for their language, so anIf-Match: "33a64df5"header or a$in a token comes through intact. Schema rows label a nullable$refasPet | nullrather thanPet | any, and a type array like["string", "integer"]asstring | integer. -
a70a0f1: API reference schema tables stay a manageable size for specs whose schemas reference each other densely. A named schema expands wherever it appears in the first two levels of nesting, as before, and below that only where it first appears; later mentions show its name. A request body of twelve schemas that each reference four others went from tens of thousands of nested tables to a few dozen at most.
-
a70a0f1: API reference code samples, the Try it panel, and the AsyncAPI composer fill server
{variables}with their declared defaults, sohttps://{region}.api.example.combecomes a real URL, and the built-in playground proxy allows the resulting origin instead of refusing it with a 403. An operation’s ownservers, or its path’s, now replace the document’s servers in its samples and panel, and the proxy allows those origins too. -
5f5c72a: A heading inside a multi-line HTML (
<!-- … -->) or MDX ({/* … */}) comment no longer becomes the page title, a table of contents entry, or a link anchor. Titles derived from file names spell acronyms the way sidebar groups do (faq.mdis “FAQ”), and an untitled folderindexpage takes its folder’s label instead of “Index”. A root file named like a version (v3-migration.md) no longer triggers the unconfigured-version warning, and the missing content root hint now names thefilesystem()source’srootfor projects that listcontent.sources. -
8d88600: The API playground’s built-in proxy (
playground: { proxy: true }) now works under abasePathor adeployment.base: the endpoint mounts underbasePathat{basePath}/_api-proxy, and the Send button targets it with the deployment base included. -
a70a0f1: The Try it panel no longer blocks Send on valid request bodies: it doesn’t ask for
readOnlyproperties the spec marks required, acceptsnullin nullable fields, and doesn’t check one type of a field that allows several. The panel also works when the browser blocks storage (Safari’s “Block All Cookies”, sandboxed iframes), where it now simply doesn’t remember credentials, and the AsyncAPI composer closes its WebSocket when a client-side navigation leaves the page. The request and response tabs move with the arrow keys, Home, and End, and the reference’s spacing follows right-to-left layouts. -
0befec9: The
posthog()analytics adapter only sends its own$pageviewon client-router navigations while PostHog captures page loads alone. Withcapture_pageview: "history_change"or adefaultsdate such as"2025-05-24", PostHog tracks navigations itself and each one is no longer counted twice, andcapture_pageview: falsenow means no pageviews at all. -
591c39e: Export → PDF (and any print of a page) keeps callouts, panels, and changelog update headings. The print stylesheet now hides only the site chrome (banner, header, sidebar, table of contents, assistant panel) instead of every
<header>and<aside>on the page. -
591c39e: Screen readers get a name for every piece of header and content chrome. An image-only logo (
logo: { image, text: "" }) names the home link after the image’salt, or the site title when it has none. A navigation selector’s accessible name includes the option it shows (“Version: v2”), matching the language switcher.<GithubInfo>labels its star and fork counts. The first “Copied” after each page load is announced reliably, since the status region now registers before its first message. -
65edd91: A
node()server build answers a configured302or307redirect with that status, as the Cloudflare Worker already does, instead of a permanent301or308that browsers cache. Anetlify()static build forces its_redirectsrules (/old /new 301!), so Netlify issues the redirect instead of serving the redirect page at the old path with a200; the_redirectsforcloudflare()and for a build with no named host stays unforced, since Cloudflare rejects the flag. -
3b5e90a: A
githubReleases()source rewrites links in release notes that point at the site’s owndeployment.siteto root-relative paths, so changelog pages follow preview deploys and the deployment base and no longer tripblume audit’s “Internal link hardcodes the site’s own origin” check. -
e80b141: The Left and Right arrow keys follow the reading direction in
<Tabs>and<Tree>: on a right-to-left page, Left moves to the next tab and expands a tree folder, and Right moves to the previous tab and collapses a folder. -
65edd91: SVG images a content source downloads are now served with
Content-Security-Policy: sandboxandX-Content-Type-Options: nosniffbyvercel(),netlify(), andcloudflare()server builds too, each through its host’s own mechanism: a route in Vercel’s routing config, a header rule in Netlify’s.netlify/v1/config.json, and the Worker Blume puts in front of Astro’s on Cloudflare. The/blume-assetsroute prerenders, so its own headers never reached a deployed site. Anode()server now matches those SVGs by the file it serves rather than the raw URL, so an encoded spelling (abc.sv%67,abc%2Esvg) or a path outsidedeployment.baseno longer skips the sandbox, and static builds addnosniffbeside it. -
4f9d1bf: The Algolia sync now declares
localeandversionfor faceting (asfilterOnly), keeping any facets you set yourself, so search on a multi-language or versioned site finds results. The search dialog’s section for pages outside every sidebar group is now labeled in the reader’s language through the newsearch.docsUI string, and the WebMCPsearch_docstool returns plain text, with characters like<and&no longer HTML-escaped. -
591c39e: Sidebar sections loaded on first open show their icons on every page, where an icon could come out blank on a page that hadn’t used it itself. A drill-in panel loaded on first open keeps its Back button and section title. Drilling into a panel or going back moves keyboard focus into the panel that slides in (its Back button, or the row that opened the panel you left), and each drill-in row reports whether its panel is open with
aria-expandedandaria-controls. -
5f5c72a: A sidebar group’s path now comes from its folder, so a page with a frontmatter
slugno longer moves its whole folder out of its tab (aguides/page withslug: installleft every page under a/guidestab with an empty sidebar). A foldermeta.tspageslist now wins over a listed subfolder’s ownorder, as it already did over a listed page’ssidebar.order, and a listed position no longer reports aBLUME_DUPLICATE_SIDEBAR_ORDERwarning against an unlisted sibling’s numeric prefix orsidebar.order. -
4f9d1bf: The agent Markdown (
.mdmirrors,llms-full.txt, MCPget_page) and the search index now read component props liketitle={...}as literal data and never run them, on.mdxand plain.mdpages and on content from CMS and GitHub release sources alike. Strings, numbers, booleans, arrays, objects, template strings, andfrontmatter.*references resolve as before; any other expression, such as a function call, leaves the component as written. -
591c39e: A header tab with
itemsrenders as a dropdown: its items open in a menu from the header, and expand in place under the tab in the mobile navigation drawer. The tab’spathstill scopes the sidebar and marks it as current. Before,navigation.tabs[].itemswas accepted but every tab rendered as a plain link. -
e80b141:
<Tabs>deep links (?install=windows,#windows) now open the linked tab when the reader arrives through a client-side navigation, not only on a full page load, and a tab no longer opens because the previous page’s URL carried a matching hash. Ats2jsfence inside a<CodeGroup>now becomes one of the group’s tabs, labeled with the fence title, instead of showing its TypeScript and JavaScript pair under every tab. -
e80b141: A
<Tooltip>stays open while the pointer moves from its trigger up to the panel, so a mouse can reach the panel’s link. The panel now shifts to stay inside the viewport near either edge of the screen, and hidden tooltips no longer widen the page, which let a whole page scroll sideways on phones. -
0befec9:
blume translate --codexruns Codex with the same lockdown asblume eval: no shell, exec, or local-image tool, and no inherited environment. Before,--sandbox read-onlystill let the translator’s shell read any file the user could. -
abb982a:
blume translatewrites into a locale folder that already exists in another casing (pt-br/for a configuredpt-BR) instead of creating a second one beside it, which on a case-sensitive filesystem published every page twice. -
0befec9:
blume translateleaves archived-version folders’meta.tsfiles alone, like the rest of a frozen snapshot. Drafts count as content: a hand-written translation markeddraft: trueis adopted instead of overwritten, and a source page that is a draft for a while keeps its ledger entries, so its outdated translations still read as stale. A partial that is one code block validates without its own fences being stripped, and folder titles from two sources with the same folder name each get their own translation.A
blume.translations.jsonwith unresolved merge-conflict markers now fails with a clear error instead of reading as empty, which let--checkpass over outdated translations. Line endings are normalized before hashing, so a CRLF checkout doesn’t look like an edit to every page. -
351d6ab: Config validation reports
content.root(orinclude/exclude) besidecontent.sourceseven while a source entry is still in the Blume 1{ type }form, soblume upgradeandblume doctorlist both on the first run. Ablumerangeblume upgradecan’t bump, such ascatalog:or annpm:alias, is reported with what to change by hand (the catalog entry inpnpm-workspace.yaml, or the alias’s version) instead of as ready.