Skip to content

Instantly share code, notes, and snippets.

@tjdevries
Created May 22, 2026 16:44
Show Gist options
  • Select an option

  • Save tjdevries/14a3395746208af5a4d5ae19b7d5390b to your computer and use it in GitHub Desktop.

Select an option

Save tjdevries/14a3395746208af5a4d5ae19b7d5390b to your computer and use it in GitHub Desktop.
// Counter browser program — compiler-known TEA shape.
// init/update return model only; `command` statements emit; handlers live in command block.
program Counter {
model {
count: Int,
step: Int,
is_resetting: Bool,
}
message {
:clicked_increment,
:clicked_decrement,
:clicked_reset,
:clicked_reset_after_delay,
:changed_step(Int),
:reset_completed,
}
command {
:schedule_reset { after_ms: Int } => {
use Browser
Browser.set_timeout(~after_ms, fn() {
send(:reset_completed);
});
}
}
fn init() {
return { count: 0, step: 1, is_resetting: false };
}
fn scaled_total(model: Model): Int = model.count * model.step
fn update(model: Model, message: Message) {
return match message {
| :clicked_increment -> { model with count: model.count + model.step }
| :clicked_decrement -> { model with count: model.count - model.step }
| :clicked_reset -> { model with count: 0, is_resetting: false }
| :clicked_reset_after_delay -> {
command :schedule_reset { after_ms: 1200 };
{ model with is_resetting: true };
}
| :changed_step(step) -> {
if step < 1 {
return model;
}
{ model with step };
}
| :reset_completed -> { model with count: 0, is_resetting: false }
};
}
fn view(model: Model): Html {
let total = scaled_total(model);
let reset_label = if model.is_resetting {
"Resetting…"
} else {
"Reset after delay"
};
return html.main(
~class: "counter-app",
[
html.h1([], ["Browser counter"]),
html.p(
~class: "counter-display",
~aria_live: "polite",
[Int.to_string(model.count)],
),
html.p(
~class: "counter-derived muted",
["Scaled total: " ++ Int.to_string(total)],
),
html.div(
~class: "counter-controls",
[
html.button(
~type: "button",
~class: "btn",
~onclick: :clicked_decrement,
["−"],
),
html.button(
~type: "button",
~class: "btn btn-primary",
~onclick: :clicked_increment,
["+"],
),
html.button(
~type: "button",
~class: "btn",
~onclick: :clicked_reset,
["Reset now"],
),
],
),
html.label(
~for: "step-input",
["Step"],
),
html.input(
~id: "step-input",
~type: "number",
~min: "1",
~value: Int.to_string(model.step),
~oninput: fn(raw) {
let step = Int.parse(raw) else 1;
return :changed_step(step);
},
),
html.button(
~type: "button",
~class: "btn",
~disabled: model.is_resetting,
~onclick: :clicked_reset_after_delay,
[reset_label],
),
],
);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment