ratatui-zonekit 0.1.1

Extensible zone and plugin rendering system for ratatui — named zones, plugin-owned panes, floating overlays, safe delegation
Documentation

ratatui-zonekit

Crates.io docs.rs CI codecov License: MIT

Extensible zone and plugin rendering system for ratatui.

Let plugins own UI zones in your TUI application. Plugins declare what they need (tabs, panels, overlays), and the host decides where and how to render them — safely.

The Problem

Building an extensible TUI means plugins need to render their own content. But giving plugins raw Frame access is unsafe — a misbehaving plugin can draw anywhere, crash the app, or ignore your theme.

The Solution

use ratatui_zonekit::{ZonePlugin, ZoneId, ZoneRequest, RenderContext};
use ratatui::buffer::Buffer;
use ratatui::layout::Rect;

struct SprintPlugin;

impl ZonePlugin for SprintPlugin {
    fn id(&self) -> &str { "bmad.sprint" }

    fn zones(&self) -> Vec<ZoneRequest> {
        vec![
            ZoneRequest::tab("bmad.sprint", "Sprint").with_order(10),
            ZoneRequest::sidebar("bmad.hooks", "Hooks").with_order(20),
        ]
    }

    fn render(&self, _zone_id: ZoneId, ctx: &RenderContext, area: Rect, buf: &mut Buffer) -> bool {
        use ratatui::widgets::{Paragraph, Widget};
        Paragraph::new("Sprint status here")
            .style(ctx.base_style)
            .render(area, buf);
        true
    }
}

The host renders plugins safely:

use ratatui_zonekit::{ZoneRegistry, SafeRenderer};
use std::sync::Arc;

let mut registry = ZoneRegistry::new();
let plugin = Arc::new(SprintPlugin);
registry.register(plugin);

// In the render loop, for each zone:
// SafeRenderer::render(&plugin, zone_id, &ctx, area, buf);
// If the plugin panics, the zone shows "[plugin crashed]" instead.

Features

Zone Types

Zone Description Example
Tab Replaces main content when active Sprint board, file tree, logs
Sidebar Panel in sidebar column Plugin status, hooks, config
Control Panel in control column (wide) Agent controls, actions
Overlay Floating popup on top Search, picker, modal
StatusBar Single line at bottom Plugin status text

Safe Rendering

Every plugin render is wrapped in catch_unwind. A crashing plugin shows an error message in its zone — the rest of the application continues normally.

Theme-Agnostic

Zonekit passes RenderContext.base_style (a plain ratatui::style::Style) to plugins. Works with:

  • ratatui-themekit: ctx.base_style = theme.style_base()
  • Raw styles: ctx.base_style = Style::default().bg(Color::Rgb(...))
  • Any theme system: just set the Style

Plugins that want richer theme access can depend on a theme crate directly — zonekit doesn't require or prevent this.

Event Routing

Plugins receive events for their zones:

fn on_event(&mut self, zone_id: ZoneId, event: &ZoneEvent) -> bool {
    match event {
        ZoneEvent::Key { code, .. } => { /* handle key */ true }
        ZoneEvent::Click { x, y } => { /* handle click */ true }
        _ => false
    }
}

Design Principles

  • Request, don't mutate: plugins request zones, the host approves
  • Safe delegation: catch_unwind isolates plugin panics
  • Theme-agnostic: works with any styling system
  • Model B: plugins declare intent, host renders
  • Zero opinion on layout: zonekit manages zones, not the overall layout

License

MIT