Skip to content

Render

Textagram rendering is host-neutral. The core produces semantic rows; each host decides how those rows become terminal cells, DOM spans, or editor decorations.

This page is a skeleton for the render integration guide. It names the contract and the host responsibilities first; deeper examples can be filled in as the render API stabilizes.

Render contract

The host-facing render surface is built around ScreenFrame, RenderLine, RenderSegment, and SemanticStyle.

#[cfg_attr(target_arch = "wasm32", derive(serde::Serialize))]
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ScreenFrame {
    pub rows: Vec<RenderLine>,
    pub width: u16,
    pub height: u16,
}
textagram/src/app/render.rs:136-142
#[cfg_attr(target_arch = "wasm32", derive(serde::Serialize))]
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct RenderLine {
    pub segments: Vec<RenderSegment>,
}
textagram/src/app/render.rs:77-81
#[cfg_attr(target_arch = "wasm32", derive(serde::Serialize))]
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct RenderSegment {
    pub text: String,
    pub style: SemanticStyle,
}
textagram/src/app/render.rs:70-75
#[cfg_attr(target_arch = "wasm32", derive(serde::Serialize))]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SemanticStyle {
    Plain,
    Grid,
    Text,
    Connector,
    Corruption,
    Cursor,
    Selection,
    HelpHeading,
    HelpKey,
    HintKey,
}
textagram/src/app/render.rs:13-26

The important boundary is:

  • the core decides what text and styles should appear
  • the host decides the concrete palette, DOM structure, terminal style, font, and incremental update strategy

SemanticStyle is intentionally semantic. Cursor means “draw this as the cursor”; it does not prescribe a specific color.

Frame composition

Most standalone hosts ask the session for a full framed screen:

    pub fn composed_frame(&self, area: Area, hints: HintProfile) -> ScreenFrame {
textagram/src/session.rs:144-144

Hosts that need small host-owned chrome, such as a file name or save prompt, use:

    pub fn composed_frame_with_overrides(
        &self,
        area: Area,
        hints: HintProfile,
        chrome: &ChromeOverrides,
    ) -> ScreenFrame {
textagram/src/session.rs:148-153
#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub struct ChromeOverrides {
    pub left_title: Option<RenderLine>,
    pub top_center: Option<RenderLine>,
}
textagram/src/app/render.rs:130-134

The shared compositor owns the common Textagram frame:

  • top border and title row
  • centered mode or transient message
  • right-side coordinates or mode-specific header text
  • canvas/help body
  • bottom/footer hint row
  • viewport clip indicators

Hosts should not duplicate that layout unless they are intentionally embedding only the canvas.

Canvas-only rendering

Some embedded hosts do not want the full Textagram chrome. Monaco is the current example: it composes editor-native surrounding UI and consumes canvas rows directly.

The canvas-only contract should stay consistent with the full-frame contract: rows are still RenderLines, segments still carry SemanticStyle, and the host still owns the final visual mapping.

TODO: document the stable canvas-row and footer-row methods once the public embedded-host surface is finalized.

Style precedence

The render tree is already composed before it reaches the host. Hosts should not reinterpret layer precedence. In broad terms, the core composes:

  1. committed shapes
  2. committed text
  3. committed connectors
  4. transient overlays such as cursor, selection, previews, and diagnostics

Invalid runtime overlaps may render as SemanticStyle::Corruption, but that is a presentation diagnostic. It is not persisted or exported as document content.

Terminal adapter

The terminal host paints a ScreenFrame into ratatui:

Renders a Textagram session into the current ratatui frame.

pub fn draw_session_frame(session: &Session, frame: &mut Frame<'_>, chrome: &ChromeOverrides) {
tg/src/terminal_render.rs:12-13

The adapter’s responsibilities are narrow:

  • ask the session for a composed frame with terminal hint profile and chrome overrides
  • map each SemanticStyle to a ratatui Style
  • write each rendered row into the ratatui buffer
  • clear any unused cells inside the terminal area

The terminal adapter should not contain editing rules.

Browser adapter

The browser host receives a serialized frame through the wasm wrapper, then maps each RenderLine to HTML.

Current browser responsibilities:

  • call TextagramSession::get_frame(...)
  • map SemanticStyle values to stable CSS classes
  • escape text content and preserve spaces
  • update only rows whose rendered HTML changed
  • schedule UI ticks requested by the session

The browser adapter should not infer selection, cursor, or corruption state from glyphs. That information is already present in the segment styles.

Sizing and ticks

Rendering depends on host-measured dimensions. Hosts update viewport/frame size before asking for a frame, then honor any timer request returned by input or reported by the session.

Key integration points:

  • session.set_frame_size(width, height) for full-frame hosts
  • session.set_canvas_viewport_size(width, height) for embedded canvas hosts
  • session.next_ui_tick_delay_ms() and session.tick_ui(elapsed_ms) for transient render animation

Test strategy

Render tests should check the semantic contract, not a particular host color theme.

Useful coverage layers:

  • core frame tests for row text and SemanticStyle spans
  • adapter parity tests that compare terminal output with ScreenFrame
  • web/wasm tests that protect serialized style names and class mappings
  • e2e/demo traces for glyph output plus optional highlight masks

Open details

  • Document the final public embedded-host render surface once Monaco no longer needs transitional glue.
  • Decide whether browser renderer helpers should be documented as the canonical DOM adapter or only as one reference implementation.
  • Add a compact style table after the CSS/ratatui mappings settle.