blume 1.3.1 → 1.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +72 -0
- package/dist/cli/index.js +3512 -814
- package/dist/cli/index.js.map +99 -87
- package/dist/types/core/base-path.d.ts +5 -0
- package/dist/types/core/config-input.d.ts +82 -6
- package/dist/types/core/i18n-ui.d.ts +2 -0
- package/dist/types/core/schema.d.ts +19 -2
- package/dist/types/core/sources/types.d.ts +5 -0
- package/dist/types/core/types.d.ts +4 -3
- package/docs/02-deployment.mdx +1 -1
- package/docs/configuration/ai.mdx +15 -1
- package/docs/configuration/index.mdx +26 -0
- package/docs/configuration/search.mdx +1 -3
- package/docs/content/i18n.mdx +13 -1
- package/docs/content/navigation.mdx +11 -0
- package/docs/reference/cli.mdx +4 -0
- package/docs/reference/frontmatter.mdx +33 -0
- package/docs/reference/meta.ts +1 -1
- package/docs/reference/translate.mdx +80 -0
- package/package.json +22 -1
- package/src/ai/agent-readability.ts +7 -4
- package/src/ai/ask-context.ts +3 -6
- package/src/ai/component-markdown.ts +7 -6
- package/src/ai/mcp/data.ts +10 -4
- package/src/ai/mcp/server.ts +74 -3
- package/src/ai/mcp/tools.ts +2 -2
- package/src/astro/generate.ts +4 -13
- package/src/astro/integration.ts +3 -1
- package/src/astro/islands.ts +4 -1
- package/src/astro/markdown-negotiation.ts +5 -0
- package/src/astro/templates.ts +69 -22
- package/src/audit/checks/indexability.ts +3 -6
- package/src/audit/checks/robots.ts +18 -37
- package/src/audit/crawl.ts +49 -49
- package/src/audit/image-size.ts +13 -53
- package/src/audit/report.ts +22 -33
- package/src/audit/types.ts +6 -2
- package/src/audit/url.ts +5 -10
- package/src/cli/commands/build.ts +129 -24
- package/src/cli/commands/dev.ts +9 -21
- package/src/cli/commands/doctor.ts +9 -22
- package/src/cli/commands/translate.ts +300 -0
- package/src/cli/env.ts +6 -52
- package/src/cli/index.ts +2 -0
- package/src/cli/init/scaffold.ts +15 -28
- package/src/cli/internal-error.ts +11 -11
- package/src/components/Icon.astro +2 -7
- package/src/components/content/Step.astro +3 -8
- package/src/components/content/Tab.astro +20 -1
- package/src/components/islands/ask-ai.tsx +25 -100
- package/src/components/islands/hooks.ts +10 -3
- package/src/components/layout/LanguageSwitcher.astro +2 -1
- package/src/components/layout/Logo.astro +4 -4
- package/src/components/layout/PageActions.astro +12 -7
- package/src/components/layout/RootLayout.astro +37 -109
- package/src/components/layout/Search.astro +18 -25
- package/src/components/layout/search/orama.ts +3 -1
- package/src/components/layout/search/types.ts +4 -16
- package/src/components/openapi/helpers.ts +21 -75
- package/src/core/base-path.ts +9 -0
- package/src/core/component-overrides.ts +0 -7
- package/src/core/config-input.ts +84 -6
- package/src/core/config.ts +3 -3
- package/src/core/diagnostics.ts +10 -20
- package/src/core/fs-atomic.ts +22 -0
- package/src/core/graph.ts +46 -2
- package/src/core/i18n-ui.ts +2 -0
- package/src/core/i18n.ts +31 -0
- package/src/core/nav-diagnostics.ts +13 -34
- package/src/core/project-graph.ts +13 -2
- package/src/core/schema.ts +174 -74
- package/src/core/sources/github-releases.ts +29 -26
- package/src/core/sources/mdx-remote.ts +10 -57
- package/src/core/sources/normalize.ts +25 -12
- package/src/core/sources/notion.ts +17 -23
- package/src/core/sources/types.ts +5 -0
- package/src/core/tsconfig-aliases.ts +39 -172
- package/src/core/types.ts +4 -3
- package/src/core/ui-packs/ar.ts +42 -1
- package/src/core/ui-packs/bg.ts +42 -1
- package/src/core/ui-packs/bn.ts +42 -1
- package/src/core/ui-packs/ca.ts +44 -1
- package/src/core/ui-packs/cs.ts +42 -1
- package/src/core/ui-packs/da.ts +42 -1
- package/src/core/ui-packs/de.ts +42 -1
- package/src/core/ui-packs/el.ts +44 -1
- package/src/core/ui-packs/es.ts +44 -1
- package/src/core/ui-packs/fa.ts +42 -1
- package/src/core/ui-packs/fi.ts +42 -1
- package/src/core/ui-packs/fr.ts +44 -1
- package/src/core/ui-packs/he.ts +42 -1
- package/src/core/ui-packs/hi.ts +42 -1
- package/src/core/ui-packs/hr.ts +42 -1
- package/src/core/ui-packs/hu.ts +42 -1
- package/src/core/ui-packs/id.ts +42 -1
- package/src/core/ui-packs/it.ts +44 -1
- package/src/core/ui-packs/ja.ts +44 -1
- package/src/core/ui-packs/ko.ts +44 -1
- package/src/core/ui-packs/nl.ts +42 -1
- package/src/core/ui-packs/no.ts +42 -1
- package/src/core/ui-packs/pl.ts +42 -1
- package/src/core/ui-packs/pt-br.ts +44 -1
- package/src/core/ui-packs/pt.ts +44 -1
- package/src/core/ui-packs/ro.ts +42 -1
- package/src/core/ui-packs/ru.ts +42 -1
- package/src/core/ui-packs/sk.ts +42 -1
- package/src/core/ui-packs/sr.ts +42 -1
- package/src/core/ui-packs/sv.ts +42 -1
- package/src/core/ui-packs/th.ts +44 -1
- package/src/core/ui-packs/tr.ts +42 -1
- package/src/core/ui-packs/uk.ts +42 -1
- package/src/core/ui-packs/vi.ts +44 -1
- package/src/core/ui-packs/zh-tw.ts +44 -1
- package/src/core/ui-packs/zh.ts +44 -1
- package/src/deploy/adapter-output.ts +44 -5
- package/src/deploy/cloudflare-negotiation.ts +527 -0
- package/src/deploy/redirects.ts +13 -0
- package/src/deploy/rss.ts +4 -1
- package/src/deploy/sitemap.ts +3 -1
- package/src/eval/agents.ts +1 -1
- package/src/eval/report.ts +20 -28
- package/src/markdown/directives.ts +6 -18
- package/src/markdown/index.ts +1 -6
- package/src/markdown/package-commands.ts +0 -4
- package/src/openapi/parse.ts +11 -9
- package/src/search/documents.ts +11 -0
- package/src/search/facets.ts +33 -0
- package/src/search/orama-index.ts +48 -6
- package/src/search/popular-icon.ts +33 -0
- package/src/theme/icon-kind.ts +20 -0
- package/src/translate/agents.ts +51 -0
- package/src/translate/ledger.ts +142 -0
- package/src/translate/meta.ts +149 -0
- package/src/translate/prompts.ts +95 -0
- package/src/translate/report.ts +354 -0
- package/src/translate/run.ts +357 -0
- package/src/translate/validate.ts +171 -0
- package/src/translate/work-list.ts +0 -0
- package/src/deploy/xml.ts +0 -8
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,77 @@
|
|
|
1
1
|
# blume
|
|
2
2
|
|
|
3
|
+
## 1.4.1
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 8865982: The built-in Ask AI panel now streams through the public `useAskAI` hook instead of carrying its own near-identical copy of the client — one implementation now owns request shaping, the optimistic assistant bubble, stale-stream and abort guards, and the error-body-is-not-an-answer rule. `useAskAI` gains an optional `errorMessage` option so custom UIs (and the built-in panel, which passes its localized dictionary string) can control the failure notice shown in the transcript.
|
|
8
|
+
- dc5c97a: Measure WebP and AVIF images in the audit's Open Graph checks. The hand-rolled header parser only understood PNG, JPEG, and GIF, so builds whose image pipeline emits modern formats — including Blume's own sharp-based optimization — silently skipped every OG image dimension check. Dimensions now come from the image-size package, which covers 25+ formats; unknown or truncated files still yield no finding.
|
|
9
|
+
- 57e1525: Watch project inputs in `blume dev` with chokidar. Raw `fs.watch` required two documented platform workarounds — watching single files through their parent directory so rename-replace saves (vim and most atomic-save editors) don't orphan the watcher, and `recursive` handling for directories. chokidar (the watcher Vite itself uses) owns both, and one watcher now covers the pages directory, config, theme, and component override files.
|
|
10
|
+
- 9dfabd6: Respect `NO_COLOR`, `FORCE_COLOR`, and terminal detection in all CLI output. Diagnostics, the audit/eval/translate reports, and internal error reports previously emitted raw ANSI escape codes unconditionally, so piping a command to a file (`blume validate > report.txt`) or reading CI logs outside a color-capable terminal showed literal `[31m` sequences. All five hand-rolled palettes now go through consola's color utilities, which disable styling when the output is not a color-capable terminal and honor the standard `NO_COLOR`/`FORCE_COLOR` overrides.
|
|
11
|
+
- 28f2a9d: Escape HTML through one complete entity table. Four call sites each hand-rolled their own escape map with different coverage: the search popular-links icon markup escaped only `&` and `"` while building an `src` attribute, the fallback code-block renderer escaped only `&`, `<`, and `>`, and the search dialog and XML feeds carried their own full tables. All four now use html-escaper's five-entity escape (the same one Astro uses internally), so every site covers `&`, `<`, `>`, `"`, and `'` consistently.
|
|
12
|
+
- 7af2f4a: Check the Node version in `blume doctor` against the package's full `engines.node` range with semver. The previous check stripped the range down to a bare version triple and compared segments numerically, so any real range expression (`^22.12.0 || >=24`, prerelease tags) degraded into comparisons against `NaN` and the check silently stopped working.
|
|
13
|
+
- 6d7abb4: Parse `.env` files with dotenv — the same parser Vite applies to these files at build time. The previous line-based parser silently truncated multi-line double-quoted values at the first newline, corrupting PEM-style credentials (`-----BEGIN PRIVATE KEY-----` blocks) before the content scan could use them, and its escape handling diverged from what the rest of the toolchain sees in the same file. The `.env.local`/`.env` cascade from the working directory up to the repository root is unchanged, and shell/CI values still win.
|
|
14
|
+
- 016e5cf: Use the unified ecosystem's own utilities for two hand-rolled markdown helpers: directive label text extraction now goes through `mdast-util-to-string`, and the `<TypeTable>` Markdown downlevel builds its GFM table with `markdown-table`, which owns delimiter-row and cell padding rules instead of string concatenation.
|
|
15
|
+
- 738c071: Replace the hand-rolled image lightbox with medium-zoom. The `markdown.imageZoom` behavior is unchanged — click to zoom, dismiss on click/scroll/Escape, opt out per image with `data-no-zoom`, images inside links stay plain — but the FLIP transform math, natural-size capping, and transition-teardown races now belong to a 2 kB library built for exactly this. The library is lazy-loaded only on pages that contain a zoomable image, and reduced-motion preferences disable the transitions as before.
|
|
16
|
+
- 863ad44: Generate OpenAPI request examples with openapi-sampler, the generator behind Redoc. Two visible improvements over the hand-rolled sampler: `readOnly` fields no longer appear in request-body samples (they are server-generated and were previously included even though the schema declared them read-only), and format-aware placeholders replace generic ones — `email`, `uuid`, `uri`, and friends produce realistic values instead of `"<format>"`. Declared `example`/`const`/`default`/`enum` values keep their precedence, and circular `$ref` chains still terminate safely.
|
|
17
|
+
- 19de528: Derive changelog meta descriptions by parsing release notes as GitHub-flavored markdown (mdast) instead of chaining strip regexes. The regex chain mis-handled real release-note shapes: tilde fences and fences of more than three backticks leaked their code into the description, an image followed by a link containing `)` truncated wrong, and prose like `a * b` or `x > y` lost characters to a blanket punctuation strip. The parsed tree drops headings, code, and raw HTML; keeps link text and inline-code content; and the word-boundary truncation is unchanged.
|
|
18
|
+
- 4d0590b: Match remote source `include` globs with picomatch — the same engine the filesystem source already uses through tinyglobby. The remote MDX source previously compiled globs with a minimal hand-rolled translator, so the same `include` array meant different things depending on source type: negation patterns (`!drafts/**`), character classes (`[0-9]`), nested braces, and extglobs silently failed to match on remote sources. The matcher is now also compiled once per enumeration instead of once per file × pattern, which matters on large GitHub trees.
|
|
19
|
+
- 842ed9c: Remove single-call delegation wrappers left over from the library migrations: the remote source's glob-matcher factory, the search dialog's `escapeHtml`, the XML escaper module, the directive label's text collector, and the env-file `parseEnv` export now call picomatch, html-escaper, mdast-util-to-string, and dotenv directly. Wrappers that carry real behavior (the atomic-write helper's mkdir/fsync policy, the audit image sizer's null-on-unknown contract, the OpenAPI sampler's error guard) are unchanged. If you deep-imported the undocumented `escapeHtml` from `blume/components/layout/search/types`, import `escape` from html-escaper instead.
|
|
20
|
+
- 909c2fd: Fix two false-positive sources in the audit's robots.txt check by matching rules with robots-parser. The hand-rolled matcher never read `Allow:` directives, so the common lockdown pattern (`Disallow: /` plus `Allow: /docs/`) flagged every sitemap URL as blocked; and it treated each `User-agent:` line independently, so consecutive agent lines heading one rule group (as the spec defines) dropped rules that do apply to `*`. robots-parser resolves Allow/Disallow by longest match and handles agent groups correctly; wildcard and `$`-anchor behavior is unchanged.
|
|
21
|
+
- 7120f63: Parse sitemaps in the audit with fast-xml-parser instead of regex scanning. Sitemaps the audit reads can come from other generators (including remote ones during network audits), and three legal constructs were invisible to the regex scan: CDATA-wrapped `<loc>` values, numeric character references like `&`, and namespace-prefixed elements (`<sm:loc>`). All three now parse; the shallow contract — the loc list, per-loc lastmod, and urlset-vs-index detection — is unchanged.
|
|
22
|
+
- 57673ee: Parse the project's tsconfig with get-tsconfig when deriving `@/`-style Vite aliases. The hand-rolled JSONC parser could corrupt a config whose string values contained `", }"` (its trailing-comma strip ran over string contents), and its `extends` resolution accepted two forms real tsc rejects (relative paths naming a directory, bare specifiers resolved through a package `main`). get-tsconfig follows tsc's own semantics — JSONC, the full `extends` chain including TS 5.0 arrays, inherited-path rebasing, and `${configDir}` substitution, which now works in alias targets.
|
|
23
|
+
- 8e9f3d0: Make atomic file writes safe under concurrency. The translation runner, its ledger, and the runtime generator each wrote through a temp file named `<path>.<pid>.tmp` — a name that is not unique within a process, so two concurrent writers to the same target (translate lanes run up to 16-wide; staged content writes fan out in parallel) could interleave through a shared temp file. All three now write through npm's write-file-atomic, whose temp names are unique per call and which preserves file modes on overwrite.
|
|
24
|
+
|
|
25
|
+
## 1.4.0
|
|
26
|
+
|
|
27
|
+
### Minor Changes
|
|
28
|
+
|
|
29
|
+
- 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.
|
|
30
|
+
- 7e9f7dd: Add `blume translate`: agent-driven i18n translation with a committed freshness ledger and a CI drift gate.
|
|
31
|
+
|
|
32
|
+
`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.
|
|
33
|
+
|
|
34
|
+
- 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 `type`s (`["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.
|
|
35
|
+
- 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.
|
|
36
|
+
|
|
37
|
+
### Patch Changes
|
|
38
|
+
|
|
39
|
+
- 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).
|
|
40
|
+
|
|
41
|
+
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.
|
|
42
|
+
|
|
43
|
+
- 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.
|
|
44
|
+
|
|
45
|
+
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.
|
|
46
|
+
|
|
47
|
+
- 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.
|
|
48
|
+
|
|
49
|
+
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.
|
|
50
|
+
|
|
51
|
+
- 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.
|
|
52
|
+
- 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}"`).
|
|
53
|
+
- b62278b: Emit `_headers` on a Cloudflare server build, so the agent-discovery surface it already generates is actually advertised.
|
|
54
|
+
|
|
55
|
+
`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`.
|
|
56
|
+
|
|
57
|
+
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.
|
|
58
|
+
|
|
59
|
+
Two related corrections fell out of testing it against a real Cloudflare server build, and both apply to static builds as well:
|
|
60
|
+
|
|
61
|
+
- 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.
|
|
62
|
+
- 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.
|
|
63
|
+
|
|
64
|
+
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.
|
|
65
|
+
|
|
66
|
+
- 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.
|
|
67
|
+
- 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.
|
|
68
|
+
- 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.
|
|
69
|
+
- 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.
|
|
70
|
+
- 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.
|
|
71
|
+
- 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.
|
|
72
|
+
- 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.
|
|
73
|
+
- 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.
|
|
74
|
+
|
|
3
75
|
## 1.3.1
|
|
4
76
|
|
|
5
77
|
### Patch Changes
|