Terminal UI
Build beautiful, interactive terminal applications with ARO's Terminal UI system. Create live-updating dashboards, system monitors, and CLI tools that respond instantly to data changes—without polling. The reactive Watch pattern triggers UI re-renders when events occur or data changes, making your terminal applications both efficient and responsive.
Getting Started
Terminal applications can access terminal capabilities and apply ANSI styling through templates:
(* templates/status.screen *)
{{ "=== Dashboard ===" | bold | color: "cyan" }}
Terminal: {{ <terminal: columns> }}×{{ <terminal: rows> }}
{{ "Success!" | color: "green" | bold }}
{{ "Warning" | color: "yellow" }}
{{ "Error" | color: "red" }}
The Terminal Object
Templates automatically have access to a terminal object with capability information:
{{ <terminal: rows> }} (* Terminal height *)
{{ <terminal: columns> }} (* Terminal width *)
{{ <terminal: supports_color> }} (* Boolean: color support *)
{{ <terminal: is_tty> }} (* Boolean: connected to TTY *)
Responsive Design Example:
{{when <terminal: columns> > 120}}
(* Wide layout *)
{{ "=== Detailed Dashboard ===" | bold }}
{{when <terminal: columns> > 80}}
(* Medium layout *)
{{ "=== Dashboard ===" | bold }}
{{else}}
(* Narrow layout *)
{{ "Dashboard" }}
{{end}}
Template Filters
Apply ANSI styling with simple filters that can be chained together:
Color Filters
{{ "Success!" | color: "green" }}
{{ "Error!" | color: "red" }}
{{ "Warning" | color: "yellow" }}
{{ "Highlight" | bg: "blue" }}
{{ "Alert" | color: "white" | bg: "red" }}
(* RGB colors (24-bit true color) *)
{{ "Custom" | color: "rgb(100, 200, 50)" }}
Named Colors: black, red, green, yellow, blue, magenta, cyan, white, brightRed, brightGreen, brightBlue, success (green), error (red), warning (yellow), info (blue)
Style Filters
{{ "Important" | bold }}
{{ "Subdued" | dim }}
{{ "Emphasis" | italic }}
{{ "Link" | underline }}
{{ "Removed" | strikethrough }}
Chaining Filters
{{ "SUCCESS" | color: "green" | bold }}
{{ "ERROR" | color: "red" | bold | underline }}
{{ "Debug Info" | color: "cyan" | dim }}
Reactive Watch Pattern
The Watch pattern is ARO's approach to live-updating terminal UIs. Unlike traditional polling, Watch is purely reactive—handlers trigger only when actual changes occur.
Repository Observer Watch
UI updates automatically when repository data changes:
(Application-Start: Task Manager) {
(* Initialize tasks *)
Create the <task1> with { id: 1, title: "Write docs", status: "pending" }.
Store the <task1> into the <task-repository>.
Keepalive the <application> for the <events>.
Return an <OK: status> for the <startup>.
}
(* Watch handler - triggers on repository changes *)
(Dashboard Watch: task-repository Observer) {
Clear the <screen> for the <terminal>.
Retrieve the <tasks> from the <task-repository>.
Transform the <output> from the <template: templates/dashboard.screen>.
Log <output> to the <console>.
Return an <OK: status> for the <render>.
}
Flow:
- App stores/updates/deletes data in repository
- Repository emits
RepositoryChangedEvent - Watch handler detects change for
task-repository - Handler retrieves fresh data and renders template
- Updated display appears in terminal
Result: Every time data changes, the dashboard automatically re-renders!
Event-Based Watch
Watch handlers can also trigger on custom domain events:
(Application-Start: System Monitor) {
Create the <metrics> with { cpu: 23, memory: 45, disk: 67 }.
Emit a <MetricsUpdated: event> with <metrics>.
Keepalive the <application> for the <events>.
Return an <OK: status> for the <startup>.
}
(* Watch handler - triggers on MetricsUpdated events *)
(Dashboard Watch: MetricsUpdated Handler) {
Clear the <screen> for the <terminal>.
Transform the <output> from the <template: templates/monitor.screen>.
Log <output> to the <console>.
Return an <OK: status> for the <render>.
}
Why Watch is Better Than Polling
❌ Traditional Polling (Other Languages)
setInterval(() => {
const tasks = getTasks();
renderDashboard(tasks);
}, 1000); // Check every second - wasteful!
- Wastes CPU cycles checking when nothing changed
- Updates delayed until next poll
- Must choose between responsiveness and efficiency
✅ ARO Watch Pattern
(Dashboard Watch: task-repository Observer) {
Retrieve the <tasks> from the <task-repository>.
Transform the <view> from the <template: dashboard.screen>.
Log <view> to the <console>.
Return an <OK: status>.
}
- Zero CPU usage when idle
- Instant updates when data changes
- No timers to manage
Terminal Actions
Clear Action
Clear the terminal screen or current line:
Clear the <screen> for the <terminal>.
Clear the <line> for the <terminal>.
Common Usage: Clear before re-rendering in Watch handlers to prevent screen clutter.
Prompt Action
Request text input from the user:
(* Basic input *)
Prompt the <name> from the <terminal>.
(* Hidden input for passwords *)
Prompt the <password: hidden> from the <terminal>.
Select Action
Display an interactive menu:
Create the <options> with ["Red", "Green", "Blue", "Yellow"].
(* Single selection *)
Select the <choice> from <options> from the <terminal>.
(* Multi-selection *)
Select the <choices: multi-select> from <options> from the <terminal>.
Complete Example
A task management dashboard that updates reactively:
main.aro
(Application-Start: Task Dashboard) {
(* Initialize tasks *)
Create the <task1> with {
id: 1,
title: "Implement feature",
status: "in-progress",
priority: "high"
}.
Create the <task2> with {
id: 2,
title: "Write tests",
status: "pending",
priority: "medium"
}.
Store the <task1> into the <task-repository>.
Store the <task2> into the <task-repository>.
Keepalive the <application> for the <events>.
Return an <OK: status> for the <startup>.
}
(* Reactive dashboard *)
(Dashboard Watch: task-repository Observer) {
Clear the <screen> for the <terminal>.
Retrieve the <all-tasks> from the <task-repository>.
(* Filter by status *)
Filter the <done> from <all-tasks> where <status> = "done".
Filter the <in-progress> from <all-tasks> where <status> = "in-progress".
Transform the <output> from the <template: templates/dashboard.screen>.
Log <output> to the <console>.
Return an <OK: status> for the <render>.
}
(* Update task - triggers Watch handler *)
(Complete Task: TaskCompleted Handler) {
Extract the <task-id> from the <event: taskId>.
Retrieve the <task> from the <task-repository> where id = <task-id>.
Update the <task: status> with "done" into the <task-repository>.
Return an <OK: status> for the <completion>.
}
templates/dashboard.screen
{{ "╔══════════════════════════════════════════╗" }}
{{ "║ " }}{{ "TASK DASHBOARD" | bold | color: "cyan" }}{{ " ║" }}
{{ "╚══════════════════════════════════════════╝" }}
{{ "📊 Statistics:" | bold }}
{{ "✓ Done: " }}{{ <done> | length | color: "green" }}
{{ "◷ In Progress: " }}{{ <in-progress> | length | color: "yellow" }}
{{ "🔄 In Progress" | bold | color: "yellow" }}
{{for task in in-progress}}
{{ " [" }}{{ <task: id> }}{{ "] " }}{{ <task: title> | bold }} {{ "(" }}{{ <task: priority> | color: "magenta" }}{{ ")" }}
{{end}}
{{ "✅ Completed" | bold | color: "green" }}
{{for task in done}}
{{ " [" }}{{ <task: id> }}{{ "] " }}{{ <task: title> | dim | strikethrough }}
{{end}}
{{ "Last updated: reactively on data changes" | dim }}
Platform Support
| Platform | Support | Notes |
|---|---|---|
| macOS | ✅ Full | All features supported (iTerm2, Terminal.app) |
| Linux | ✅ Full | All features supported (GNOME Terminal, Konsole, etc.) |
| Windows Terminal | ✅ Full | Full ANSI support |
| CMD/PowerShell | ⚠️ Partial | Limited ANSI support (Windows 10+) |
Graceful Degradation: ARO automatically detects terminal capabilities and falls back: RGB → 256-color → 16-color → no color, Unicode → ASCII, etc.
Best Practices
Check Capabilities
{{when <terminal: supports_color>}}
{{ <error> | color: "red" | bold }}
{{else}}
{{ "ERROR: " }}{{ <error> }}
{{end}}
Responsive Layouts
{{when <terminal: columns> > 120}}
(* Wide: detailed 3-column layout *)
Transform the <view> from the <template: templates/wide.screen>.
{{when <terminal: columns> > 80}}
(* Medium: 2-column layout *)
Transform the <view> from the <template: templates/medium.screen>.
{{else}}
(* Narrow: stacked layout *)
Transform the <view> from the <template: templates/narrow.screen>.
{{end}}
Efficient Re-Rendering
(Dashboard Watch: data-repository Observer) {
(* Clear before full re-render for clean display *)
Clear the <screen> for the <terminal>.
Retrieve the <data> from the <data-repository>.
Transform the <view> from the <template: dashboard.screen>.
Log <view> to the <console>.
Return an <OK: status>.
}
Quick Reference
| Feature | Syntax |
|---|---|
| Watch (Repository) | (Name Watch: repository Observer) |
| Watch (Event) | (Name Watch: EventType Handler) |
| Color Filter | {{ <text> | color: "red" }} |
| Background Color | {{ <text> | bg: "blue" }} |
| Bold | {{ <text> | bold }} |
| Dim | {{ <text> | dim }} |
| Italic | {{ <text> | italic }} |
| Underline | {{ <text> | underline }} |
| Strikethrough | {{ <text> | strikethrough }} |
| Terminal Rows | {{ <terminal: rows> }} |
| Terminal Columns | {{ <terminal: columns> }} |
| Clear Screen | Clear the <screen> for the <terminal>. |
| Prompt Input | Prompt the <input> from the <terminal>. |
| Select Menu | Select the <choice> from <options> from the <terminal>. |