A terminal UI framework for MoonBit, inspired by bubbletea (Go) and Elm architecture. Provides a Model-Update-View pattern with typed messages, ANSI rendering, and composable components.
Dependencies

moon test
moon run src/examples/showcaselet quit = binding(keys=["q", "ctrl+c"], help_key="q", help="quit")
let model = help_model(
keymap=keymap([quit]),
show_full=false,
width=80,
)
let footer = help_view(model)let program = Program::new(
init=my_init,
update=my_update,
view=my_string_view,
)
.with_structured_view(fn(model) {
View::text(my_string_view(model))
.with_window_title("Pippa")
.with_cursor_visible(true)
})src/ # Source root (moon.mod.json → source: "src")
├── moon.pkg # Core library package
├── types.mbt # Cmd, UpdateResult, WindowSize, Program
├── message.mbt # KeyMsg, MouseMsg, InputEvent
├── command.mbt # Command helpers and composition
├── program.mbt # Program[Model, Msg] entry point
├── ansi.mbt # ANSI escape sequence helpers
├── style.mbt # Style, Color, borders, and placement
├── view.mbt # col/row/lines/text/gap/hgap layout DSL
├── component/ # Composable component sub-package
│ ├── moon.pkg
│ └── ... # Spinner, textarea, viewport, progress, etc.
└── examples/
├── hello/ # Minimal example app
├── structured-view/ # View.content plus per-frame terminal state
└── showcase/ # Rich interactive demomoon check # Type-check the project
moon fmt # Format all source files
moon info # Refresh generated package interfaces
moon test # Run all tests
moon test --update # Run tests and update snapshots
moon run src/examples/hello # Run the hello example
moon run src/examples/structured-view # Run the structured View example
moon run src/examples/showcase # Run the showcase demopub(all) struct ClipboardReply {
selection : ClipboardSelection
payload : ClipboardPayload
} derive(Eq, Debug)type Cmd[Msg]pub(all) struct EnhancedKeyMsg {
code : EnhancedKeyCode
shifted : EnhancedKeyCode?
base_layout : EnhancedKeyCode?
modifiers : Array[EnhancedKeyModifier]
event_type : EnhancedKeyEventType
text_codepoints : Array[Int]
} derive(Eq, Debug)pub(all) enum InputEvent {
Key(KeyMsg)
EnhancedKey(EnhancedKeyMsg)
Mouse(MouseMsg)
Focus
Blur
Paste(String)
TerminalReply(TerminalReplyMsg)
Unknown(Bytes)
} derive(Eq, Debug)pub(all) enum InternalMsg {
KeyPress(Array[Byte])
WindowResize(WindowSize)
Lifecycle(LifecycleMsg)
QuitRequested
} derive(Eq, Debug)pub(all) struct MouseMsg {
button : MouseButton
action : MouseAction
modifiers : Array[MouseModifier]
col : Int
row : Int
} derive(Eq, Debug)pub(all) struct Program[Model, Msg] {
init : () -> UpdateResult[Model, Msg]
update : (Model, Msg) -> UpdateResult[Model, Msg]
view : (Model) -> String
structured_view : (Model) -> View?
sub : (InputEvent) -> Msg?
on_tick : () -> Msg?
mouse : Bool
alt_screen : Bool
focus_reporting : Bool
bracketed_paste : Bool
hide_cursor : Bool
synchronized_output : Bool
rendering : Bool
read_stdin : Bool
input : () -> Array[InputEvent]?
raw_input : () -> Bytes?
cancelled : () -> Bool?
fps : Int
window_width : Int
window_height : Int
output : (String) -> Unit
on_window_size : (WindowSize) -> Msg?
on_resize : (Int, Int) -> Msg?
should_quit : (Model) -> Bool
catch_interrupt : Bool
on_lifecycle : (LifecycleMsg) -> Msg?
exec_runner : (ExecProcess) -> ExecResult
suspend_runner : () -> Unit
}fn[Model, Msg] Program::new(init~ : () -> UpdateResult[Model, Msg], update~ : (Model, Msg) -> UpdateResult[Model, Msg], view~ : (Model) -> String, sub? : (InputEvent) -> Msg?, on_tick? : () -> Msg?, mouse? : Bool, alt_screen? : Bool, focus_reporting? : Bool, bracketed_paste? : Bool, hide_cursor? : Bool, synchronized_output? : Bool, rendering? : Bool, read_stdin? : Bool, input? : () -> Array[InputEvent]?, raw_input? : () -> Bytes?, cancelled? : () -> Bool?, fps? : Int, window_width? : Int, window_height? : Int, output? : (String) -> Unit, on_resize? : (Int, Int) -> Msg?, should_quit? : (Model) -> Bool, catch_interrupt? : Bool, on_window_size? : (WindowSize) -> Msg?, on_lifecycle? : (LifecycleMsg) -> Msg?, exec_runner? : (ExecProcess) -> ExecResult, suspend_runner? : () -> Unit) -> Program[Model, Msg]fn[Model, Msg] Program::run_with_handle(program : Program[Model, Msg], on_start : (ProgramHandle[Msg]) -> Unit) -> RunResult[Model]fn[Model, Msg] Program::with_exec_runner(self : Program[Model, Msg], exec_runner : (ExecProcess) -> ExecResult) -> Program[Model, Msg]fn[Model, Msg] Program::with_input(self : Program[Model, Msg], input : () -> Array[InputEvent]) -> Program[Model, Msg]fn[Model, Msg] Program::with_lifecycle(self : Program[Model, Msg], on_lifecycle : (LifecycleMsg) -> Msg?) -> Program[Model, Msg]type ProgramHandle[Msg]pub(all) enum RunResult[Model] {
RunCompleted(Model)
RunQuit(Model)
RunInterrupted(Model)
RunRuntimeError(String)
RunExec(Model, ExecResult)
} derive(Debug)pub(all) struct Style {
fg : Int?
bg : Int?
fg_ext : Color?
bg_ext : Color?
border_fg : Color?
border_bg : Color?
bold : Bool
dim : Bool
italic : Bool
underline : Bool
blink : Bool
reverse : Bool
strikethrough : Bool
border : Border?
border_top : Bool
border_right : Bool
border_bottom : Bool
border_left : Bool
padding_top : Int
padding_bottom : Int
padding_left : Int
padding_right : Int
margin_top : Int
margin_bottom : Int
margin_left : Int
margin_right : Int
width : Int
height : Int?
min_width : Int
min_height : Int
max_width : Int?
max_height : Int?
align : Align
} derive(Eq, Debug)pub(all) enum TerminalReplyMsg {
CursorPosition(CursorPositionReply)
DeviceAttributes(Array[Int])
SecondaryDeviceAttributes(Array[Int])
Color(TerminalColorReply)
Capability(CapabilityReply)
Clipboard(ClipboardReply)
UnknownReply(Bytes)
} derive(Eq, Debug)pub(all) struct View {
content : String
cursor_position : ViewCursorPosition?
cursor_visible : Bool?
window_title : String?
cursor_style : CursorStyle?
foreground_color : Color?
background_color : Color?
progress : ViewProgress?
mouse_mode : ViewMouseMode?
} derive(Eq, Debug)fn View::new(content~ : String, cursor_position? : ViewCursorPosition?, cursor_visible? : Bool?, window_title? : String?, foreground_color? : Color?, background_color? : Color?, progress? : ViewProgress?, mouse_mode? : ViewMouseMode?, cursor_style? : CursorStyle?) -> Viewfn begin_sync_update() -> Stringfn bg_color(n : Int) -> Stringfn bg_rgb(r : Int, g : Int, b : Int) -> Stringfn center(s : String, width : Int) -> Stringfn clear_screen_below() -> Stringfn clear_terminal_progress() -> Stringfn clear_window_title() -> Stringcol <| [
"header",
row <| ["a", " ", "b"],
"footer",
]fn disable_bracketed_paste() -> Stringfn disable_focus_reporting() -> Stringfn disable_mouse() -> Stringfn enable_bracketed_paste() -> Stringfn enable_focus_reporting() -> Stringfn enable_mouse() -> Stringfn enable_mouse_cell_motion() -> Stringfn enable_mouse_motion() -> Stringfn enter_alt_screen() -> Stringfn exit_alt_screen() -> Stringfn fg_color(n : Int) -> Stringfn fg_rgb(r : Int, g : Int, b : Int) -> Stringfn gap(n? : Int) -> Stringfn hyperlink(url : String, label : String) -> Stringfn line_count(s : String) -> Intfn max_line_width(s : String) -> Intfn move_cursor(row : Int, col : Int) -> Stringfn pad_left(s : String, width : Int) -> Stringfn pad_right(s : String, width : Int) -> Stringfn place(width : Int, height : Int, content : String, align? : Align, valign? : VerticalAlign) -> Stringfn render_full(view : String, prev_lines : Int) -> Stringfn request_background_color() -> Stringfn request_capability(name : String) -> Stringfn request_cursor_position() -> Stringfn request_device_attributes() -> Stringfn request_foreground_color() -> Stringfn request_secondary_device_attributes() -> Stringfn reset_background_color() -> Stringfn reset_foreground_color() -> Stringrow <| [
stat_card("PHASE", label),
" ",
stat_card("TICKS", count),
]fn set_terminal_progress(state : Int, value : Int) -> Stringfn set_window_title(title : String) -> Stringfn strip_ansi(s : String) -> Stringfn text(s : String) -> Stringfn truncate(s : String, max_width : Int) -> Stringfn visible_width(s : String) -> Intfn word_wrap(text : String, width : Int) -> StringA terminal UI framework for MoonBit, inspired by bubbletea (Go) and Elm architecture. Provides a Model-Update-View pattern with typed messages, ANSI rendering, and composable components.
Dependencies