---
changelog:
  category: Release
  version: blume@1.4.1
date: '2026-08-09T03:40:56Z'
seo:
  description: >-
    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…
title: blume@1.4.1
type: changelog
---
### Patch Changes

- 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.
- 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.
- 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.
- 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.
- 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.
- 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.
- 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.
- 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.
- 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.
- 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.
- 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.
- 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.
- 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.
- 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.
- 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 `&#38;`, 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.
- 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.
- 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.
