Skip to content

blume@1.6.0

Choose a tag to compare

@github-actions github-actions released this 04 Sep 05:43
· 14 commits to main since this release
a76fecf

Minor Changes

  • 089f0d2: Native GraphQL API reference. Point the new top-level graphql config block at a schema — SDL text or an introspection JSON result, local or remote — and Blume generates one real page per root field (grouped as Queries, Mutations, and Subscriptions) plus one page per named type (Objects, Input Objects, Enums, Interfaces, Unions, and custom Scalars), all in the sidebar, search, llms.txt, and OG images like any hand-written doc. Operation pages show arguments with defaults and deprecations, a generated example operation with typed variables and a matching example response, live code samples, and the same Try it playground the OpenAPI reference ships — a plain JSON POST of { query, variables } against the configured endpoint, with playground.proxy support for CORS-restricted APIs. Type pages cross-link every type reference and list where each type is used.

  • 65e3853: Add github.host so docs hosted on a GitHub Enterprise instance get working repo links. Every repo-derived URL — the header mark, per-page edit links, the agent manifest's repository, the OG card's slug, and <GithubInfo> — was built against a hardcoded github.com, so an Enterprise repo produced links into the public site. host defaults to https://github.com, must be an HTTP(S) URL, and is normalized to its origin so nothing lands mid-link. The REST base <GithubInfo> queries is derived from it (an api. subdomain for an Enterprise Cloud data-residency tenant, /api/v3 for Enterprise Server) or set outright with github.api, and a host prop points one card at a different instance. <GithubInfo> withholds its token from a cleartext-HTTP API base rather than sending it over the wire, and warns once per base so a private repo's missing counts are explained.

  • 2fa67b8: Custom heading anchors and table-of-contents markers, matching Fumadocs' syntax so migrated content works verbatim. Append [#custom-id] to any heading to pin its anchor id — the marker never renders, links keep working after a heading is reworded, and pinned anchors stay identical across translated locales, where auto-generated ids differ per language. [!toc] keeps a heading on the page but out of the table of contents; [toc] does the reverse, adding a TOC-only entry that renders as an invisible anchor target so its link still scrolls somewhere — useful for labeling sections built from components rather than prose. Markers chain in any order (## Heading [toc] [#id]), work in Markdown and MDX, are stripped from search indexing, and blume validate resolves anchor links against pinned ids (including case-sensitive ones). Marker parsing is always on: a heading that previously ended in literal marker-shaped text now parses as marked — wrap the bracket text in inline code (## Using `[toc]`) to render it literally. The one exception is CommonMark's own: a trailing bracket whose label has a link-reference definition on the page ([toc]: /url, including one nested in a block quote or list item) is a shortcut reference link and stays in the heading text; a bare [toc]: with no destination defines nothing.

  • beec2d2: Add navigation.actions and navigation.cta — plain links in the header, and the one filled button. navigation.featured pins secondary links to the top of the sidebar and there was no header slot at all, so a docs site arriving from a hosted platform lost whatever its old top bar asked the reader to do. cta is singular by contract. Both hide below the sm breakpoint, where the header has room for the logo and the drawer toggle and nothing else — except cta on a page with no navigation toggle, where it stays, since nothing else can surface it on a phone.

  • beec2d2: navigation.repo now takes a URL as well as a boolean. github drives the per-page edit link, the header mark and the agent manifest's repository together, so a project whose docs repo is private had to unset all three — and then had no way to show a mark at all. An absolute URL points it anywhere on GitHub, an organization for instance, while true keeps deriving it from github and false still hides it.

  • a669f80: The Notion source now targets @notionhq/client v5 and Notion's data source API: a database is read through its first data source. Upgrade the peer dependency with npm install @notionhq/client@^5.

  • 9ec4de4: Add a built-in obsidian content source. Point it at an Obsidian vault and Blume reads the notes in place — no export step, and nothing generated into your repo:

    export default defineConfig({
      content: {
        sources: [{ type: "obsidian", vault: "vault", prefix: "notes" }],
      },
    });
    • [[Wikilinks]] become route links, addressed by note name across the whole vault the way Obsidian addresses notes: custom link text ([[Note|label]], and [[Note\|label]] inside a table), heading anchors ([[Note#Install]], [[#Install]]), block references ([[Note#^id]]), full and partial paths ([[folder/Note]], [[Note.md]]), and a frontmatter slug. A name two notes share resolves to an exact path match first, then to the first note in vault order, and warns only when a link actually resolves through the collision.
    • Heading anchors resolve against the target note's real headings, matched the way Obsidian writes them (inline formatting stripped) and slugged by the same pass that fills the page manifest.
    • Frontmatter keeps Blume's page meta plus any key declared in frontmatter.extend or, for notes of that type, in a content type's frontmatter; every other Obsidian property (including tags, aliases, and cssclasses) is dropped, so a vault written with the Properties UI builds cleanly.
    • Locale directories and version snapshots inside the vault publish under their locale and version like filesystem content.
    • An unresolved wikilink degrades to plain text with a warning, and one to a missing heading keeps the page link; single-line %%comments%% are stripped and a wikilink inside an HTML comment is left alone; fenced, indented, and inline code pass through verbatim; symlinked folders are followed; untitled notes take their filename as the title; dot-folders (.obsidian, .trash, plugin caches) and the usual never-content directories are skipped by both the scan and the dev watcher; blume version cut leaves a vault inside content.root out of the snapshot.
    • Vault notes get git "Last updated" dates when the vault is inside the repository, and "Edit this page" links through github.dir for a vault beside a monorepo docs app.

    blume init offers the source and seeds a first note. Not yet lowered: callouts, embeds, multi-line comments, aliases as link targets, and backlinks.

    Also in this release: frontmatter's stringify no longer re-parses a string body (a body opening with a --- divider was read as a second front matter block), and git last-modified dating now skips the scan entirely when no local source exposes a content root — a custom staged source that sets sourcePath without a contentRoot is no longer dated — set contentRoot on the source to date its pages.

  • 20a5da5: Content includes: <include>./path.mdx</include> splices another file into a page at build time — the same syntax Fumadocs ships, so migrated content works unchanged. Markdown/MDX targets are embedded as content (front matter stripped, nested includes with cycle detection, relative images rebased onto the including page); any other extension — or an explicit lang attribute — embeds the file as a fenced code block, with meta for the fence meta string. Paths resolve relative to the including file, or from the content root with a leading /. Spliced content flows through every surface: headings join the page's table of contents and anchor index, search indexes the included text, the .md/.mdx mirrors and llms-full.txt serve the expanded document, broken links inside a partial are reported against the partial file, and editing a partial in blume dev reloads the pages that include it. Underscore-prefixed files (_snippets/) are already excluded from routing, so partials need no configuration. Unresolvable statements surface as BLUME_INCLUDE_NOT_FOUND, BLUME_INCLUDE_CYCLE, and BLUME_INCLUDE_OUTSIDE_ROOT diagnostics.

Patch Changes

  • 68f48c1: Downlevel components in the Ask AI grounding corpus, the way the .md mirror, llms-full.txt and MCP get_page already do. Ask AI was built from the search documents' verbatim Markdown, so a page whose body is a <CardGroup> of <Card>s reached the model as JSX with no prose in it — and section landing pages, which are exactly that shape, rank first for section-level questions. prop={frontmatter.*} expressions now resolve here too.
  • fd08e26: Require Astro 7.2.3+, which renames the dev SSR entry to virtual:astro:server-app so full reloads no longer re-request it with a spurious .js suffix — and drop the serverAppResolvePlugin shim that worked around the old id corrupting the dev server on content renames.
  • 1715b14: Give each AsyncAPI channel or operation that references the same component its own copy before traits merge. The aliases shared one object, so the first merge leaked into the second and stripped traits from the component table entry itself.
  • 0fe3d43: Darken the green and orange text that misses WCAG AA in light mode. Both drew at -700 over a 15% tint of their own hue, landing at 4.32:1 and 4.44:1 against the 4.5:1 bar for text that size, while every other hue already cleared it (teal 4.69:1, red 5.17:1, blue 5.71:1, purple 5.82:1, violet 6.00:1, yellow 6.02:1). At -800 they reach 6.20:1 and 6.25:1, with the fill weight unchanged. This covers the GET and PUT method badges (and GraphQL QUERY), the matching badges in a reference's sidebar, 2xx and 4xx response status chips, and <Badge>'s success and warning variants. The deprecated label on an operation moves too: at orange-600 on the page background it was 3.59:1, the worst of the set. Dark mode was already passing and is unchanged.
  • 0fe3d43: Darken the callout icons that miss the WCAG 1.4.11 non-text contrast bar in light mode. A callout's icon names its type, so it is meaningful UI rather than decorative: check/success and warning sat at 2.94:1 and 2.95:1 against their own tint and now take -700 (4.52:1 and 4.67:1), and note's icon was muted-foreground at 70% over the muted surface (2.68:1) and is now full strength (4.64:1). Dark mode was already passing.
  • 4c0fdfc: Keep camelCase word boundaries in API reference operation routes. An operationId went through the generic slugger, which lowercases before it replaces non-slug characters, so processSlackInteraction collapsed to /reference/integrations/processslackinteraction. Operation ids are usually camelCase because OpenAPI generators reuse them as SDK method names, and renaming them just to get readable URLs couples SDK naming to presentation. The key now hyphenates the boundaries first: a lower-case letter or digit followed by a capital, and the last capital of an acronym before a capitalized word, so processSlackInteraction becomes process-slack-interaction and getHTTPResponse becomes get-http-response. Already kebab-case ids and the method-plus-path fallback are unchanged. GraphQL field and type names and AsyncAPI operation ids derive their routes through the same key, so a GraphQL addPet mutation moves from /graphql/mutations/addpet to /graphql/mutations/add-pet and an AsyncAPI publishUserSignedup operation from publishusersignedup to publish-user-signedup.
  • 0fe3d43: Darken the typed <Card> icons and the stroked <Badge> variants that still missed the light-mode contrast bar. A card's check, warning, and note icons carried the same values the callout icons moved off (2.94:1, 2.95:1, and 2.68:1 against their tint) and now share the callout table. A stroked badge has no fill of its own and inherits whatever surface it sits on — inside a note callout, success at -700 was 4.47:1 — so success and warning now take -800 whether filled or stroked.
  • 68f48c1: Downlevel <Card> and <CardGroup> to Markdown on the agent-facing surfaces. Both are Blume's own components and neither had a serializer, so a section landing page — the shape blume init scaffolds and the Mintlify migrator emits — published raw JSX to /<route>.md, llms-full.txt, MCP get_page and the Ask AI corpus. A card becomes its title as a link over its body, with the cta last; a group becomes everything it holds, block by block. Serializers in ai.markdownComponents gain childBlocks() — every direct child of the element in order, each already downleveled — which is what CardGroup is built on.
  • e823872: Harden two scanners against pathological input: HTML comments stripped before anchor detection are replaced with a space so their neighbors can't splice into new markup, and github.dir plus source/meta route prefixes trim slashes linearly instead of with a regex that was quadratic on a run of /.
  • ce20984: Make room for the copy button in code blocks that have no language bar to hold it. Inside a component whose chrome is not-prose<Tabs>, <CodeGroup>, <Steps>, <Callout>, <Card>, <Accordion> and the rest — the bar is cleared but the button is not, and the block's padding-top was the 1rem chosen for a block with no chrome at all, so the button painted over the first line of code. The same applied to an untitled <CodeBlock> in plain prose, which renders no bar. Both now reserve the button's strip, as does a raw <pre><code> block written in prose, which gets a button but has no language bar either; the clearance is keyed on the docs layout that injects the button, so a custom page layout without it keeps the plain inset.
  • 35b5c6a: Support trailing {#custom-id} heading markers as an equivalent of [#custom-id] — verbatim in .md, and as the escape \{#custom-id\} in .mdx, where a bare {…} is a JSX expression. blume check now reports a bare {#id} in an .mdx page (or in a partial it includes) as BLUME_MDX_CURLY_ANCHOR instead of leaving it to fail at compile time, and blume translate is told to preserve the marker. Fragment-link validation also recognizes ids on raw HTML elements (<a id="…">, including tags wrapped over several lines) as anchor targets, ignoring ids inside code, inline code, HTML comments, <Prompt> blocks, and component props.
  • ce20984: Drop the page-actions divider when the rail has no table of contents. The actions block carries its own top border as a separator under the contents, but the contents only render when a page has a heading within toc.minHeadingLeveltoc.maxHeadingLevel — so on a page without one the block drew a rule across the top of an empty column. The rail now decides once whether an outline renders and passes that to the block (divider prop, default on), so the two cannot disagree.
  • 1715b14: Keep a spaced Shiki line range (```ts {1, 3-5}) as one fence-meta token. The range used to split on its whitespace, and the 3-5} fragment was promoted to the code block's title.
  • ce20984: Cap the search dialog's section-filter row so the results keep the dialog. The dialog is a fixed height and the filter chips wrapped without a bound, so on a site with many sections the chips took the height and the results list got the remainder — measured on an 18-section corpus, the row wanted 266px of the 480 and left about one visible result. The row now shows two rows of chips plus the top of a third as the scroll cue, chips no longer wrap on long section names, the selected chip is scrolled into view when the chips reorder as the query narrows, and each chip exposes its selected state as aria-pressed.
  • 6d4ffec: Load the font subsets your locales need. Remote fonts used to load only the Latin subset, so Vietnamese, Central European, Cyrillic, and Greek text fell back to the system font. Blume now derives subsets from i18n.locales, and remote families accept a subsets option to pin the list.
  • 1715b14: Treat an empty graphql.endpoint like an omitted one. The playground and code samples fall back to the endpoint placeholder instead of targeting an empty URL.
  • ceb7a9f: Fix the image lightbox becoming impossible to close after the first in-page navigation. medium-zoom injected its stylesheet into <head> once, when its module first loaded, and the client router's head swap discarded it on the next navigation; with the transition rule gone, the close animation's transitionend never fired, so Escape, a backdrop click, and scrolling all left the zoomed image open (and the zoom-in cursor disappeared) until a hard reload. The stylesheet now ships in the layout's page CSS, which the router carries across swaps, and the library loads as its pure build with no runtime injection.
  • 03f5b36: Upgrade js-yaml to v5. Front matter parses exactly as before: dates still become Date objects and << merge keys still resolve.
  • aee337e: JSON-LD can now say what a site is and who runs it. seo.organization adds an Organization node to every page — name and URL default to the site's, with a logo, sameAs profiles, the email and telephone as a ContactPoint, and the address as a PostalAddress — which the WebSite and article nodes cite as publisher. seo.software adds a SoftwareApplication node to the homepage with the product's name, description, category, operating system, license, an Offer when a price is set (0 for free software), and registry or repository sameAs links; software: true takes every default. Both need deployment.site, and custom pages built on PageLayout or RootLayout pick them up automatically.
  • dff7930: Scan link-reference definitions in linear time. A line of deeply nested block-quote or list containers that never reaches a [label]: no longer backtracks exponentially while headings are extracted.
  • aee337e: llms.txt now tells agents when to use the site and where its machine-readable surface lives. A new ai.llmsTxt.details option inserts free-form Markdown after the title and summary — the llms.txt spec's details block — for "when to use" guidance, the install command, or the package name in the site's own words. The generated file also closes with two sections that need no configuration: Agent skills lists each skill published through ai.skills with its description, and Agent resources links every artifact the build emits — llms-full.txt, the per-page .md mirror, the MCP server and its discovery document, the skills index, the API catalog, agent-readability.json, and the sitemap — each only when it exists.
  • aee337e: The hosted MCP server now exposes every page as an MCP resource alongside its tools. resources/list enumerates the pages at their served URLs (a blume: URI when no deployment.site is configured) with a text/markdown type, title, and description; resources/read returns the page's agent Markdown — the same output get_page serves — and accepts a listed URI, a bare route, or a .md mirror URL. An unknown URI answers with the protocol's resource-not-found code. The server card advertises the resources capability, so clients and readiness scanners that attach context by URI can browse the docs without calling a tool.
  • 1715b14: Corrections to the blume-migrate skill: a Docusaurus or Fumadocs folder with collapsible: false maps to that folder's display: "flat" instead of being dropped, several Fumadocs GraphQL sources map to graphql.sources rather than a single spec, a Nextra docsRepositoryBase on a non-github.com origin sets github.host to that origin, and the favicon convention reads icon.{svg,png,ico} or favicon.{svg,png,ico}.
  • 24ac208: Darken --blume-muted-foreground from oklch(0.54 0 0) to oklch(0.53 0 0) and lighten the tip tint on callouts and cards from accent/10 to accent/6 so muted body text clears WCAG AA on every tinted surface, not just on the page. At 0.54 muted text was 5.06:1 on the background but 4.38:1 inside a danger callout and 4.50:1 inside an info one; at 0.53 those rise to 4.57:1 and 4.69:1 and the page to 5.28:1. tip tints with the configurable accent, whose default is near black, so no text lightness alone could make it safe — even at 0.53 it sat at 4.25:1 over the 10% tint. At 6% it clears 4.5:1 against any accent (4.62:1 worst case on pure black). Dark-mode tokens are unchanged.
  • aee337e: The default 404 page now helps readers and agents recover instead of dead-ending. Under a "Where to look next" heading it links every top-level section (each tab's resolved target), the sitemap when a deployment.site makes one possible, and llms.txt when it's enabled — so an agent that followed a stale URL lands on a real HTTP 404 whose body points at the site map and the docs index. The three new labels (notFound.suggestions, notFound.sitemap, notFound.llms) are translatable through ui like the rest of the page's copy.
  • 43931fb: Localize the "Open in chat" prompt. The prompt handed to the chat provider ("Read <url> so I can ask you questions about this page.") was hardcoded in English regardless of the site's locale. It now comes from the UI dictionary as actions.openInChatPrompt — with {url} replaced by the page's raw-Markdown URL at load time — so localized sites send a localized prompt, every shipped starter pack carries a translation, and i18n.ui can override the wording like any other chrome string.
  • ce20984: Keep long unbreakable tokens inside the content column. Prose sat at overflow-wrap: normal, so an API permission, a broker list, a bare URL used as its own link text or a module-qualified name in a generated reference painted past the column — and nothing between the paragraph and the viewport clips it, so the run landed in the document's scroll width and dragged the whole page sideways on a phone. Headings had their own opt-in; the property is now set once on the prose root and inherited, so running text, headings and inline code are covered by the same rule. Tables are unchanged: an auto-layout column always grows to fit its widest token, so a wide table still relies on its scroll wrapper.
  • 5559319: Close the site's dropdowns — the page actions (Export, Open in chat, Connect to MCP), the header language switcher and the nav selectors — when clicking outside them, pressing Escape, or moving keyboard focus out of the panel, instead of leaving the panel open until its own trigger is clicked again. Escape returns focus to the trigger when pressed from inside the menu, and an Escape aimed at the search dialog no longer closes a menu beneath it.
  • ce20984: Give the API reference's sticky sample panel its own scroll region. A grid item's sticky containing block is its grid row, and the row is as tall as its tallest item — so expanding a request schema on the left made the left column tower over the panel and handed sticky an enormous travel budget, pinning the panel with everything below the viewport fold unreachable until the left column ended. Bounding it to the viewport turns the overhang into a scroll. The tradeoff runs the other way on an operation with a short left column and a long sample: the row is then only as tall as the capped panel, and the overhang is reached by scrolling the panel rather than the page; a playground response is scrolled into view when it lands below that fold. The scroll region is styled like the sidebar and the table of contents — thin, border-colored thumb, inset from the Request card's edge, stable gutter — wheel input chains to the page once the panel reaches its end, and the focus ring on the panel's first control is no longer clipped by the scroller's edge. Applies to the OpenAPI, AsyncAPI and GraphQL operation pages, which now share one OperationPanel component.
  • 1715b14: Give the API playground proxy a 30-second upstream deadline, shared across a redirect chain. The client already aborted its own request after 30 seconds, but that never reached the server-side fetch, so a documented API that accepted the connection and never answered held a server request slot until the platform killed it. A timed-out upstream is now the same JSON 502 as an unreachable one.
  • 1715b14: Emit the request body in the playground's JavaScript and Python code samples as the raw editor text, never re-read as a JavaScript object literal or Python dict. The generated sample now sends byte-for-byte what the live request sends: a __proto__ key stays a key, an id past 2^53 keeps its digits, and 1e400 is not re-serialized as Infinity.
  • beec2d2: A protocol-relative navigation.featured href (//host/path) no longer picks up a locale prefix on non-default locales, where it became /fr//host/path. localizePath now draws the same line as withBasePath: a leading slash alone doesn't make a route.
  • fe966ca: Redesign the default 404 page: a two-column layout with the title and home link on the left and a vertical "Where to look next" list of sections, sitemap, and llms.txt on the right, stacking on small screens.
  • e2153d5: Restore live Markdown hot reload in migrated .-rooted projects. Astro's content watcher now honors the docs collection's negated globs, so the generated dev config no longer hides Astro's cache dir from the watcher — the escape hatch that previously cost .md body edits a dev-server restart in that layout.
  • 2bfe840: Accept @sanity/client v8 as a peer dependency for the Sanity content source.
  • fc24f9d: Keep a Scalar-rendered API reference on the page's theme. Scalar decided light or dark on its own — from its own localStorage key or the OS setting — and never read Blume's data-theme, so a reader whose stored preference disagreed with what their browser reports to prefers-color-scheme (Brave with its own dark color scheme over a light OS, for one) got a light navbar over a dark reference, with two toggles that didn't agree. The reference layout now pins Scalar's color mode to the current theme before it mounts, which also hides Scalar's own switch, and mirrors later flips of Blume's toggle onto the embed. A scalar.forceDarkModeState or scalar.darkMode set through the escape hatch is left to Scalar, as before.
  • dd1ed31: Add search.indexing.includeCodeBlocks to index fenced code (body and title) in the source-built search indexes — the client index, hosted syncs, and the MCP search_docs tool — while keeping the plain-text default. .mdx pages are now parsed as MDX for indexing, and components are downleveled with the same serializers the agent surfaces use, so prose and fences inside <Steps>/<Tabs> and the text a <Card> or <TypeTable> shows are indexed instead of being folded into raw HTML.
  • 1715b14: Keep an explicit script subtag when a search locale carries a POSIX suffix. az_Cyrl.UTF-8 fell back to az, which maximizes to Latin script, so segmentation stayed off and Cyrillic content produced no search tokens; sr_Latn.UTF-8 did the reverse. The codeset and modifier suffix is now dropped before the tag is parsed.
  • 52ac092: Add a syncKey prop to Tabs: only groups sharing the same key switch together, so unrelated groups that happen to share a tab title stay independent. Nested tab groups now also keep their own panels instead of having them adopted by an ancestor group.
  • d009a19: Keep the dark theme on the page during client-side navigation. The client router replaces the root element's attributes with the incoming page's, which dropped data-theme until Blume re-applied it after the swap; in between, the router's scroll restoration computed the new page's styles with the light palette, so the sidebar, header tabs, language selector, search button, feedback buttons, previous/next links and page actions visibly animated from light to dark on every navigation. The theme is now stamped onto the incoming document before the swap, so it never drops.
  • ce20984: Emit data-depth on each table-of-contents item. The heading's level was computed and then written only into an inline padding-inline-start, so a project wanting to style the rail by level had to match on that style string — which breaks silently if the indent ever changes.
  • 52ac092: Add ts2js code fences: mark a ts or tsx block with the ts2js meta keyword and it renders as synced TypeScript/JavaScript tabs, with the JavaScript variant generated automatically — type syntax and type-only imports stripped, formatting, comments, and JSX preserved.
  • 72c9abe: Parse the project's tsconfig.json path aliases with the TypeScript compiler API instead of get-tsconfig. Blume's get-tsconfig v4 and Astro's pinned v5 left two copies in every install, and a stale CI node_modules cache could drop Astro's nested copy, failing blume build with The requested module 'get-tsconfig' does not provide an export named 'readTsconfig'. TypeScript already ships with Blume, so there is now one fewer dependency and alias resolution follows tsc exactly.
  • aee337e: blume build now audits the Vercel function bundle after a server build and fails when the bundle is missing a package its server code imports. The Vercel adapter's dependency trace silently drops any bare import it can't resolve from the project root — under an isolated linker (pnpm, Bun's isolated mode) that includes Blume's own runtime dependencies, such as the MCP server's @modelcontextprotocol/sdk — and the deployed function then fails on every request with FUNCTION_INVOCATION_FAILED while the build, CI, and warm-cache previews all stay green. The build now reports each missing package with the chunk that imports it and the npm install -D … line that fixes it; a missing Blume dependency fails the build, a project's own missing external is a warning.