Your First App
Your first Ansiq app should be smaller than the showcase examples, but still real enough to demonstrate the model.
It should include:
- one
App - one message path
- one local reactive state
- one input widget
- one visible UI update
Here is a small teaching example:
use ansiq::prelude::*;
use ansiq::{run_app, view};
#[derive(Clone, Debug)]
enum Message {
Submit(String),
}
#[derive(Default)]
struct NotesApp {
submitted: String,
}
impl App for NotesApp {
type Message = Message;
fn render(&mut self, cx: &mut ViewCtx<'_, Self::Message>) -> Element<Self::Message> {
let draft = cx.signal(|| String::new());
let current = draft.get();
view! {
<Paragraph text={"Type and press Enter"} />
<Input
value={current.clone()}
on_change={{
let draft = draft.clone();
move |next| draft.set_if_changed(next)
}}
on_submit={|next| Some(Message::Submit(next))}
/>
<Paragraph
text={
if self.submitted.is_empty() {
"Last submit: (nothing yet)".to_string()
} else {
format!("Last submit: {}", self.submitted)
}
}
/>
}
}
fn update(&mut self, message: Self::Message, _handle: &RuntimeHandle<Self::Message>) {
let Message::Submit(next) = message;
self.submitted = next;
}
}
#[tokio::main(flavor = "multi_thread")]
async fn main() -> std::io::Result<()> {
run_app(NotesApp::default()).await
}The four pieces to notice
App
App owns the application lifecycle.
It has two core responsibilities:
render: describe the current UIupdate: handle messages and mutate app state
render
render does not draw to the terminal directly. It returns an Element tree that the runtime owns and updates.
cx.signal(...)
This creates or restores a stable reactive handle for local interaction state.
In this example it has one clear job:
- the current input draft lives in the local signal
Input.on_changeonly updates that draft- only submitted data is promoted into
self.submitted
It is not just a local variable. It is part of the cooperation between component scope, reactivity, and runtime continuity.
update
update turns UI events into app state changes. In this example, pressing Enter produces a Submit message and updates the app’s displayed result.
What actually happens when you type
At a high level:
Inputreceives keyboard inputon_changeupdates the signal- the signal updates local UI state
on_submitemitsMessage::Submit- runtime forwards the message into
update updatemutates app state- the next render uses the new state
This shows the basic division of labor:
- signals for local, in-progress interaction state
updatefor application-level message handling
Why both self and signal exist here
This is one of the first things people ask.
For now, use this rule:
self.submittedowns durable application statedraftowns local reactive input state
In this example the input does not need to become durable app state until the user submits it. That is why keeping it in a local signal is the more natural starting point.
If the app later needs validation, cross-component sharing, or persistence for the in-progress draft, you can promote it upward at that point.
Next
Continue with Core Concepts.
That page explains why Ansiq is runtime-first and why that matters more than the widgets alone.