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,
}#[cfg_attr(target_arch = "wasm32", derive(serde::Serialize))]
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct RenderLine {
pub segments: Vec<RenderSegment>,
}#[cfg_attr(target_arch = "wasm32", derive(serde::Serialize))]
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct RenderSegment {
pub text: String,
pub style: SemanticStyle,
}#[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,
}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 {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 {#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub struct ChromeOverrides {
pub left_title: Option<RenderLine>,
pub top_center: Option<RenderLine>,
}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:
- committed shapes
- committed text
- committed connectors
- 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) {The adapter’s responsibilities are narrow:
- ask the session for a composed frame with terminal hint profile and chrome overrides
- map each
SemanticStyleto a ratatuiStyle - 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
SemanticStylevalues 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 hostssession.set_canvas_viewport_size(width, height)for embedded canvas hostssession.next_ui_tick_delay_ms()andsession.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
SemanticStylespans - 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.