Bengal 0.2.6

Double-buffered dev server, i18n gettext workflow, Jinja2 removal, navigation and validation performance, new output generators

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)

Newbengal i18ncommand group for gettext PO/MO translation workflows:

  • bengal i18n extract — Scan templates for t() calls and generate .pottemplate files
  • bengal i18n compile — Compile .po files to .mobinary format
  • bengal i18n status — Show translation coverage per locale with color-coded percentages

The underlyingi18n/catalog.pyprovides 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.

  • Removedjinja.pyengine and adapter
  • Removed Jinja2 fromdetect_adapter_type()logic
  • If you were usingtemplate_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 for details.

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

Previous/next page lookups usedlist.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 withbisect_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 output for LLM discovery
  • robots.txtgenerator — 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: Newvisibility.ai_train and visibility.ai_input page properties for controlling AI training and RAG/grounding signals, with site-level defaults via content_signalsconfig
  • Tag normalization:Frontmatter.from_dict() and Page.__post_init__()normalize malformed tag input (e.g., nested lists, None values)
  • Menu field rename:urlhref in MenuItem.to_dict(); active/active_trailremoved (templates compute active state via URL comparison for cache stability)
  • Excerpt index: Newget_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 useCLIOutputfor consistent formatting
  • RTL support: Newdirection()template function for right-to-left language detection
  • Template validation: New--templates-contextflag for Kida context validation to catch missing variables before render
  • Subdirectory detection:bengal serve and bengal buildnow scan all subdirectories for Bengal site markers instead of hardcoding directory names
  • Dead code removal: Three unused methods removed fromPageOperationsMixin
  • Dependency bumps: kida-templates ≥0.2.8, patitas ≥0.3.5, bengal-pounce ≥0.3.0

Breaking Changes

  • Jinja2 removed: If your config specifiestemplate_engine: jinja2, change it to kida. See the Kida migration guide.
  • 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.hrefagainst the current page URL instead.

Upgrading

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