Zum Inhalt springen
Blume is now publicly available.
Blume
Deutsch
Esc
navigierenöffnen⌘Jvorschau
Auf dieser Seite

blume@1.4.0

Minor Changes

  • 6afe539: Add declared facets: content.types.<type>.facets names custom frontmatter keys whose values become filterable metadata. Faceted values ride along on search documents (blume-search.json and the MCP snapshot), and the MCP search_docs and list_pages tools accept a filters object matching against them ({"domain": "architecture", "status": "enforced"}, every entry must match) — so a knowledge base holding RFCs, runbooks, or policies can drive progressive-disclosure agent workflows straight off its static content. Results carry their facet values, list_pages shows each page’s, and the shared Orama index gains a facetTerms enum-array field so one static schema serves every project’s facet keys. Each facet name must be a declared custom key (per-type or frontmatter.extend, validated at config load), and string, number, and boolean values facet — numbers and booleans stringified.

  • 7e9f7dd: Add blume translate: agent-driven i18n translation with a committed freshness ledger and a CI drift gate.

    blume translate --claude (or --codex) finds every default-locale page that is missing or outdated in each configured locale and translates it headlessly with your local agent CLI — Blume builds the prompts, disables the agent’s tools, validates each reply’s structure (frontmatter reconstructed from the source, code-fence counts preserved), and writes the files itself. A committed blume.translations.json ledger records the source hash behind every translation, so reruns are incremental, and hand-authored translations are adopted rather than overwritten (only --force retranslates them). Under the dir parser, folder-nav meta.ts titles are translated too, copying every other key verbatim so per-locale sidebars keep their ordering. blume translate --check is the read-only CI gate: it exits non-zero when any translation is missing or stale, with --json emitting the shared diagnostics report shape.

  • 6afe539: Let MCP clients filter by content type. search_docs and list_pages accept an optional contentTypes array that narrows results to pages of the given frontmatter types (["rfc"], ["blog", "changelog"]), so an agent working against a site that mixes docs with RFCs, runbooks, or policies can scope retrieval to the kind of page it needs. Search hits now name their content type alongside the title, route, and excerpt, search documents carry the resolved type end to end (blume-search.json included), and the shared Orama index gains a contentType enum field filtered with an exact where match — the same mechanism the locale filter uses.

  • 6afe539: Add per-type frontmatter schemas via content.types.<type>.frontmatter. Where frontmatter.extend declares custom keys site-wide, a per-type declaration scopes them to pages whose frontmatter type matches — so a project can require an RFC’s status or a runbook’s service without loosening every other page. Keys follow the same rules as extend: any Standard Schema library validates them (Zod at whatever version the project installs, Valibot, ArkType), every declared key is checked on every page of the type so required schemas enforce type-wide, and validated values land on the page record’s custom field. A declaration for content.defaultType applies to pages that set no type, a key declared only for another type stays unknown elsewhere (typo-catching is unchanged), and a key can’t be declared both site-wide and per-type.

Patch Changes

  • f88d256: Honor Accept: text/markdown on Cloudflare server builds. Deployed Workers sites ignored the header — the .md mirrors were reachable only at their explicit URLs — because the ASSETS binding serves the prerendered content pages before the Worker script runs, and even a request that does reach the Worker is answered by @astrojs/cloudflare’s handler straight from that binding, ahead of the only place Astro middleware runs. No middleware could ever see a content-page request, so the negotiation now lives in the deploy bundle itself: after astro build, Blume writes a small wrapper Worker in front of the adapter’s entry and scopes assets.run_worker_first in dist/server/wrangler.json to the content routes (grouped into a handful of route globs, merged with any user-configured rules, and staying within Wrangler’s rule limits).

    When the client prefers Markdown, the wrapper serves the page’s prerendered .md mirror from the assets binding with Vary: Accept; every other request is delegated to the Astro Worker untouched, so only content routes pay the extra Worker hop. Cloudflare does not apply _headers to worker-first routes, so the wrapper re-stamps what the static layer would otherwise add on the routes it takes over — the homepage agent-discovery Link header, the homepage x-markdown-tokens estimate, and the Markdown charset=utf-8 — while negative rules keep the raw .md/.mdx URLs on the static fast path with their _headers treatment intact. The agent readability manifest now advertises contentNegotiation on Cloudflare server builds too.

  • bd939ba: Keep configured redirects off the Cloudflare worker-first routes, so they ship the status you configured instead of a permanent 301. On a server build Blume routes redirects through Astro’s own config, and @astrojs/cloudflare turns those into dist/client/_redirects entries carrying the exact status — a file only Cloudflare’s static layer reads. But assets.run_worker_first is derived from the first path segment of the content routes, so a single page at /docs/reference emits /docs/*, which swallows every redirect under /docs/docs/api, /docs/api/run-query, and so on. Inside a worker-first route the static layer never runs, _redirects is never consulted, and Astro’s SSR redirect handler answers instead. That handler honors the configured status only when the destination resolves to a discrete route, which under Blume it never does (every page is served from [...slug]), so computeRedirectStatus defaults a GET to 301. A configured 302 was therefore served as a permanent redirect that browsers cache more or less forever — the opposite of what a temporary redirect is for, and unfixable from the config.

    The generated Worker wiring now emits a negative run_worker_first rule for every configured redirect a positive rule would otherwise claim, so the static layer keeps serving it. The exemptions are derived from the merged rule set, so redirects claimed by your own run_worker_first rules are exempted too (a configured true is rewritten to its array spelling /* so the negatives can ride along), and they compare full served URLs, so subpath deploys (deployment.base) guard their content routes correctly. A redirect that no rule would have claimed gets no rule, and a path that is also a content route is never exempted — the page owns it, and taking it off the Worker would silently disable its Markdown negotiation; a docs-only site redirecting / is exempted like any other path. A retired section collapses to a {path} + {path}/* pair, which is two rules however many URLs it held, but only when no content route lives underneath, and only the request spellings a positive actually claims are emitted, conserving Wrangler’s cap of 100 rules. A rule over Wrangler’s 100-character limit collapses to its nearest safe ancestor glob instead of costing the whole site its negotiation. The exemptions ride along into the coarse fallback, where /* claims everything and they matter more rather than less; if even that cannot fit the limits the negotiation is skipped entirely, since a redirect cached as permanent is a worse outcome than raw Markdown staying at its explicit .md URL.

  • 2e231bc: Serve configured redirects from the generated Cloudflare Worker itself instead of carving them out of assets.run_worker_first. The wrapper Worker now bakes in a redirect table and answers any redirect a worker-first rule claims with its exact configured status, before delegating to the Astro Worker; redirects outside every worker-first rule still never invoke the Worker and are served by the static layer from _redirects, as before.

    Unlike the negative-rule exemptions this replaces, a baked-in table costs nothing against Wrangler’s caps of 100 rules and 100 characters — so a large or deeply nested redirect set can no longer push the rule set into the coarse fallback or cost the site its Accept: text/markdown negotiation, and redirects claimed by your own run_worker_first rules (including a bare true) are answered correctly without rewriting your configuration. A redirect at a content route’s own path is still never honored — the page owns it — and non-ASCII destinations are percent-encoded into the Location header.

  • 50a9ea7: Keep blume dev from tearing down React islands built from project components. The generated runtime is the Vite root, so user pages, islands, and alias-reachable components were invisible to the dep optimizer’s startup scan — and the Babel-injected react/compiler-runtime import can never be scanned — so their dependencies were only discovered mid-session. That re-optimization served a second React copy to islands hydrating at that moment, crashing them with “Invalid hook call”. The generated config now points the optimizer’s startup scan at user pages, the islands/ directory, and tsconfig-alias directories, and force-includes the compiler runtime, so every dependency hydration can reach is part of the first optimization run.

  • 7e9f7dd: Backfill every built-in UI language pack with the chrome strings added since launch — the Export menu, “Copy code”, “Copy Codex command”, the Ask AI panel, navigation and theme-toggle labels, the 404 page, and the newer search dialog strings — so localized sites no longer show English for those surfaces. The “Open in v0/ChatGPT/Claude/…” provider labels, previously hardcoded, now localize through a new actions.openIn template ("Open in {name}").

  • b62278b: Emit _headers on a Cloudflare server build, so the agent-discovery surface it already generates is actually advertised.

    buildHomeLinkHeader() had three consumers and every one excluded this deployment: the _headers writer returned early unless output === "static", the Vercel routing-config injection only runs for that adapter, and the middleware that calls res.setHeader("Link", …) is mounted on astro:server:setup, so it is dev-only. A Cloudflare server build therefore served no homepage Link header at all, and no Content-Type on the extensionless well-known files — an API catalog went out with no media type rather than application/linkset+json.

    The gate is now readsHeaderFiles(), which is true for any static build and additionally for a Cloudflare server build: the Worker serves dist/client through its ASSETS binding, and Workers static assets honor _headers from that directory exactly as Pages does. Node server builds stay excluded, because the standalone server’s static handler ignores the file and writing it there would be inert. Vercel server builds stay excluded because their headers arrive through the routing config, which this would duplicate.

    Two related corrections fell out of testing it against a real Cloudflare server build, and both apply to static builds as well:

    • The user opt-out is now checked at public/_headers rather than in dist. @astrojs/cloudflare writes its own _headers during the build (an immutable Cache-Control for /_astro/*), so testing dist read an adapter-generated file as a user opt-out and skipped silently — the fix above would not have fired without this.
    • When a _headers already exists in the output, its rules are preserved and the generated ones are appended, so the adapter’s caching rule and Blume’s discovery rules coexist. On a static build this changes behavior for a _headers that reached dist some way other than public/ (an integration writing it directly, say): that file previously suppressed generation entirely and is now appended to. Shipping public/_headers remains the opt-out.

    The charset rules in this file remain redundant on a server build, where the runtime endpoint sets Content-Type on the Response itself; they are harmless, because a static-asset rule only applies to a file served from that directory. The Link and well-known media-type rules are the part that was missing, and the previous comment’s reasoning — that server adapters set Content-Type themselves — was true of the charset rules only and had been applied to the whole file.

  • f9f045c: Fix blume check failing on the generated MCP endpoint when ai.mcp is enabled. JSON imports widen literal types, so mcp-data.json could never satisfy McpData’s discriminated navigation nodes; the generated endpoint now asserts the snapshot back to McpData at the JSON boundary.

  • 7e9f7dd: The header’s search field and language switcher now collapse to compact icon buttons below the lg breakpoint (previously sm), and the logo mark no longer shrinks when the header runs out of room — fixing the mid-width squeeze where the inline tab bar pressed the two controls into each other.

  • eb8e1dc: Restore the content inset on Tab panels that mix a code fence with prose. The panel dropped its padding via :has(>pre), a rule meant for code-only panels — a fence owns its own frame, so insetting it again double-pads it. But the bare selector also matched a panel holding a fence and prose, stripping the padding from the prose as well; combined with [&>:last-child]:mb-0! a trailing paragraph ended up flush against the panel border with no space on any side, and lost its left inset so text started hard against the frame. The rule is now [&:has(>pre):not(:has(>:not(pre,template)))], so p-0 applies only when the panel holds nothing but fences and mixed panels keep the inset like every other code-block host. template is excluded because a Tab with an icon renders a <template data-blume-tab-icon> that stays a permanent child — the tabs script clones its content into the trigger rather than moving the node. That rules out the two naive “is the fence alone?” tests, each for a different reason: :only-child sees two children on every icon tab and so silently reintroduces the double inset, while :only-of-type ignores the template (it only counts sibling pre elements) and therefore matches a single fence but stops matching a code-only panel holding two or more of them.

  • aa33ae7: Write a Cloudflare server build’s artifacts where the Worker can actually serve them. @astrojs/cloudflare declares preserveBuildClientDir: true, so it keeps Astro’s dist/client + dist/server split and points the ASSETS binding in the dist/server/wrangler.json it generates at ../client — the deployed Worker serves dist/client and nothing above it. Blume still treated dist/ as the served root, so robots.txt, sitemap.xml, llms.txt, llms-full.txt, agent-readability.json, .well-known/api-catalog, and the Pagefind index all landed one directory too high and 404’d in production, and blume audit crawled dist/ and read client/ as a route segment, reporting ~118 phantom broken links. deployStaticDir and its isolated-build mirror isolatedStaticDir now resolve to dist/client for a Cloudflare server build, matching the existing Node standalone handling. Cloudflare static builds are unaffected — they have no client/server split, so the outDir root is still what ships.

  • 8d4fd17: Allow search.popular icons to use image paths/URLs and inline SVG, matching nav icons. Markup is resolved on the server before the Cmd+K island runs.

  • 7e9f7dd: Header tab labels (and tab dropdown item labels) accept a per-locale map alongside the plain-string form: label: { en: "Docs", fr: "Documentation" }. Each locale’s navigation resolves its own entry, falling back to the default locale’s and then the map’s first entry, so an i18n site can translate its header without forking the config.

  • 7e9f7dd: Locales accept an optional style — freeform guidance like “Brazilian Portuguese, informal você” that rides along in every blume translate prompt, pinning register and dialect from a locale’s first translation and winning over an existing translation’s style on reruns.

  • 7e9f7dd: blume translate now shows the agent the existing translation when retranslating a stale page and instructs it to match its register, dialect, and terminology, so a small source edit yields a minimal retranslation instead of a from-scratch rewrite that can flip style.

Zuletzt aktualisiert am 8. August 2026

War diese Seite hilfreich?