---
title: Bengal 0.2.6
description: Double-buffered dev server, i18n gettext workflow, Jinja2 removal, navigation and validation performance, new output generators
type: changelog
date: 2026-03-17
draft: false
lang: en
tags: [release, changelog, server, i18n, performance, navigation, orchestration]
keywords: [release, 0.2.6, double-buffer, i18n, gettext, jinja2, navigation, wave-scheduler, output-formats]
category: changelog
---

# Bengal 0.2.6

**Key additions:** Double-buffered dev server that eliminates flash-of-unstyled-content during rebuilds, a gettext PO/MO workflow for i18n, Jinja2 engine removal (Kida is now the only template engine), O(n²)→O(1) page navigation, new output format generators, and orchestration improvements for build metrics and diagnostics.

---

## Highlights

### Double-Buffered Dev Server

The dev server now uses a double-buffered output strategy to eliminate FOUC (flash of unstyled content) during rapid rebuilds. Instead of writing directly to the output directory while the server is serving it, the build writes to an alternate buffer directory and atomically swaps it in when complete.

- **BufferManager**: New component manages two output directories and provides atomic swap
- **ASGI integration**: The ASGI app accepts a callable that returns the current active directory, so each request sees a consistent snapshot
- **Zero-disk reactive path**: Content-only edits can skip full disk writes entirely
- **In-process builds**: Dev server builds now run in-process for cleaner Ctrl+C shutdown (no zombie subprocesses)

### i18n Gettext Workflow (Phase 1A)

New `bengal i18n` command group for gettext PO/MO translation workflows:

- **`bengal i18n extract`** — Scan templates for `t()` calls and generate `.pot` template files
- **`bengal i18n compile`** — Compile `.po` files to `.mo` binary format
- **`bengal i18n status`** — Show translation coverage per locale with color-coded percentages

The underlying `i18n/catalog.py` provides PO/MO catalog management and coverage computation. Exception handling in i18n commands uses narrow, specific catches.

### Jinja2 Engine Removed

Jinja2 is no longer a built-in template engine. Kida is now the canonical (and only) engine.

- Removed `jinja.py` engine and adapter
- Removed Jinja2 from `detect_adapter_type()` logic
- If you were using `template_engine: jinja2` in your config, switch to `template_engine: kida`. Kida uses Jinja2-compatible syntax, so most templates work without changes. See the [Kida migration guide](/docs/theming/templating/kida/migration/from-jinja/) for details.

### O(n²)→O(1) Page Navigation

Previous/next page lookups used `list.index()`, scanning the full page list for every call. On a site with N pages this was O(n²) total work.

Navigation now uses lazily-built dicts keyed by page identity. The index is constructed once on first access and invalidated when the page list grows (e.g., as taxonomy pages are appended). Each lookup is O(1).

Applies to: `page.next`, `page.prev`, `page.next_in_section`, `page.prev_in_section`.

### Faster Cross-Reference Validation

The health validator previously recomputed line numbers by slicing the content string on every match. Newline positions are now precomputed once and binary-searched with `bisect_left`, reducing validation from O(M×N) to O(N + M log N) per page.

---

## New Output Generators

The postprocess pipeline gains several new output format generators:

- **`agent_manifest_generator`** — Machine-readable manifest for AI agent consumption
- **`changelog_generator`** — Generates changelog pages from structured release data
- **`llms_txt_generator`** — Produces [llms.txt](https://llmstxt.org/) output for LLM discovery
- **`robots.txt` generator** — Configurable robots.txt with sitemap references

---

## Orchestration Improvements

- **Block cache in WaveScheduler**: Per-page rendering now leverages the block cache for faster incremental builds
- **Explicit manifest context**: Asset manifest context is propagated explicitly through the orchestration pipeline (before falling back to ContextVar)
- **Aggregated fallback diagnostics**: Instead of per-asset warnings, asset manifest fallbacks are sampled and summarized at phase end — reduces log noise on large sites
- **Build metrics**: Parsed/rendered cache hit rates now appear in the build summary

---

## Other Changes

- **Content Signals**: New `visibility.ai_train` and `visibility.ai_input` page properties for controlling AI training and RAG/grounding signals, with site-level defaults via `content_signals` config
- **Tag normalization**: `Frontmatter.from_dict()` and `Page.__post_init__()` normalize malformed tag input (e.g., nested lists, None values)
- **Menu field rename**: `url` → `href` in `MenuItem.to_dict()`; `active`/`active_trail` removed (templates compute active state via URL comparison for cache stability)
- **Excerpt index**: New `get_excerpt_for_path()` in the cache layer provides lightweight excerpt lookups for PageProxy without triggering a full page load
- **list-table directive**: Fixed options mapping; previously skipped tests re-enabled
- **CLI output**: Stale process and port-in-use messages now use `CLIOutput` for consistent formatting
- **RTL support**: New `direction()` template function for right-to-left language detection
- **Template validation**: New `--templates-context` flag for Kida context validation to catch missing variables before render
- **Subdirectory detection**: `bengal serve` and `bengal build` now scan all subdirectories for Bengal site markers instead of hardcoding directory names
- **Dead code removal**: Three unused methods removed from `PageOperationsMixin`
- **Dependency bumps**: kida-templates ≥0.2.8, patitas ≥0.3.5, bengal-pounce ≥0.3.0

---

## Breaking Changes

- **Jinja2 removed**: If your config specifies `template_engine: jinja2`, change it to `kida`. See the [Kida migration guide](/docs/theming/templating/kida/migration/from-jinja/).
- **Menu dict shape**: `url` renamed to `href`; `active` and `active_trail` fields removed. Templates that read `item.url` should use `item.href`. Templates that relied on `item.active` should compare `item.href` against the current page URL instead.

---

## Upgrading

```bash
uv pip install --upgrade bengal
# or
pip install --upgrade bengal
# or use the self-update command
bengal upgrade
```
