Skip to Content
APIansiq-runtime

ansiq-runtime

ansiq-runtime is the runtime entry point for Ansiq. It owns:

  • the App lifecycle
  • RuntimeHandle as the control surface for messages, tasks, history, and focus
  • the main loop, dirty scope collection, and localized rerender
  • focus and input routing

If you have not read the conceptual pages yet, start with:

The App trait

pub trait App { type Message: Send + 'static; fn mount(&mut self, _handle: &RuntimeHandle<Self::Message>) {} fn render(&mut self, cx: &mut ViewCtx<'_, Self::Message>) -> Element<Self::Message>; fn update(&mut self, message: Self::Message, handle: &RuntimeHandle<Self::Message>); fn on_unhandled_key(&mut self, _key: Key, _handle: &RuntimeHandle<Self::Message>) -> bool { false } }

mount(...)

Call timing:

  • after the terminal session is entered
  • before the first render(...)

Good uses:

  • start background tasks
  • emit initial messages
  • establish long-lived samplers or connections

Bad uses:

  • manually patching the terminal
  • treating it as a rendering hook

render(...)

render(...) is responsible for:

  • describing the current UI tree
  • establishing reactive reads for that render
  • exposing component boundaries and continuity keys

It does not:

  • write terminal patches directly
  • decide relayout or damage tracking

update(...)

update(...) consumes Message and turns it into app state changes.

The core boundary is:

async task -> handle.emit(Message) -> update(...) -> state

on_unhandled_key(...)

If a key is:

  • not consumed by the focused widget
  • and not consumed by runtime-level focus navigation

it can still bubble back to the app through on_unhandled_key(...).

This is a good place for:

  • global shortcuts
  • Escape to cancel
  • mode switches
  • command entry
fn on_unhandled_key( &mut self, key: Key, handle: &RuntimeHandle<Self::Message>, ) -> bool { if key == Key::Esc { let _ = handle.emit(Message::Cancel); return true; } false }

RuntimeHandle

RuntimeHandle is the main control surface between the app and the runtime.

Messages and tasks

handle.emit(Message::Refresh)?; let task_handle = handle.clone(); handle.spawn(async move { let _ = task_handle.emit(Message::Loaded(data)); });

Most important methods:

  • emit(message): send a message back into the app
  • spawn(future): start background work
  • quit(): request that the main loop exits

Important rule:

  • do not mutate UI signals directly inside spawned tasks
  • the stable path is always emit -> update -> state

History and scrollback

handle.commit_history("completed turn".to_string())?; handle.commit_history_block(block)?;

Use these when:

  • a completed turn should move into scrollback
  • the live viewport should stay reserved for the active session

Focus scopes

handle.trap_focus_in("composer-panel")?; handle.clear_focus_scope()?;

Use focus scopes for:

  • modals
  • overlays
  • panels that should trap Tab traversal inside a subtree

These APIs are keyed by continuity keys, not temporary node ids.

Minimal runtime entry

use ansiq_runtime::run_app; #[tokio::main] async fn main() -> std::io::Result<()> { run_app(MyApp::default()).await }

If you also need viewport control, use run_app_with_policy(...), covered on:

Last updated on