blume 1.3.1 → 1.4.0
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 +50 -0
- package/dist/cli/index.js +3221 -201
- package/dist/cli/index.js.map +73 -61
- 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 +1 -1
- package/src/ai/agent-readability.ts +7 -4
- package/src/ai/ask-context.ts +3 -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/integration.ts +3 -1
- package/src/astro/markdown-negotiation.ts +5 -0
- package/src/astro/templates.ts +66 -18
- package/src/audit/url.ts +5 -10
- package/src/cli/commands/build.ts +129 -24
- package/src/cli/commands/translate.ts +300 -0
- package/src/cli/index.ts +2 -0
- 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/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/Search.astro +15 -20
- package/src/components/layout/search/orama.ts +3 -1
- package/src/core/base-path.ts +9 -0
- package/src/core/config-input.ts +84 -6
- 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/normalize.ts +25 -12
- package/src/core/sources/types.ts +5 -0
- 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/eval/agents.ts +1 -1
- 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 +148 -0
- package/src/translate/meta.ts +149 -0
- package/src/translate/prompts.ts +95 -0
- package/src/translate/report.ts +360 -0
- package/src/translate/run.ts +376 -0
- package/src/translate/validate.ts +171 -0
- package/src/translate/work-list.ts +0 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,55 @@
|
|
|
1
1
|
# blume
|
|
2
2
|
|
|
3
|
+
## 1.4.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 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.
|
|
8
|
+
- 7e9f7dd: Add `blume translate`: agent-driven i18n translation with a committed freshness ledger and a CI drift gate.
|
|
9
|
+
|
|
10
|
+
`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.
|
|
11
|
+
|
|
12
|
+
- 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.
|
|
13
|
+
- 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.
|
|
14
|
+
|
|
15
|
+
### Patch Changes
|
|
16
|
+
|
|
17
|
+
- 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).
|
|
18
|
+
|
|
19
|
+
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.
|
|
20
|
+
|
|
21
|
+
- 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.
|
|
22
|
+
|
|
23
|
+
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.
|
|
24
|
+
|
|
25
|
+
- 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.
|
|
26
|
+
|
|
27
|
+
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.
|
|
28
|
+
|
|
29
|
+
- 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.
|
|
30
|
+
- 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}"`).
|
|
31
|
+
- b62278b: Emit `_headers` on a Cloudflare server build, so the agent-discovery surface it already generates is actually advertised.
|
|
32
|
+
|
|
33
|
+
`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`.
|
|
34
|
+
|
|
35
|
+
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.
|
|
36
|
+
|
|
37
|
+
Two related corrections fell out of testing it against a real Cloudflare server build, and both apply to static builds as well:
|
|
38
|
+
|
|
39
|
+
- 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.
|
|
40
|
+
- 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.
|
|
41
|
+
|
|
42
|
+
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.
|
|
43
|
+
|
|
44
|
+
- 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.
|
|
45
|
+
- 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.
|
|
46
|
+
- 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.
|
|
47
|
+
- 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.
|
|
48
|
+
- 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.
|
|
49
|
+
- 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.
|
|
50
|
+
- 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.
|
|
51
|
+
- 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.
|
|
52
|
+
|
|
3
53
|
## 1.3.1
|
|
4
54
|
|
|
5
55
|
### Patch Changes
|