Re:T-UI widgets

Build small launcher tools without turning the launcher into an IDE.

Widgets are Re:T-UI-owned surfaces for quick, inspectable tools. Lua gives regular users a native scripting layer for timers, todos, notes, status panels, action chips, config forms, and command suggestions without needing Termux plumbing.

Lua widgets Config surfaces Persistent prefs Widget-local files Suggestion scripts
Mental model

Keep preview, config, and source editing separate.

A widget has a live launcher surface, optional config UI, and a script editor path. Those surfaces should stay separate so the launcher remains predictable instead of becoming a tangle of custom mini-apps.

Preview: the active module panel rendered by Lua with ui:show_text, ui:show_kv, buttons, actions, tables, or progress.
Config: a launcher-owned form declared by Lua through ui:show_config. Use it for user-editable values.
Source: the Lua editor opened explicitly through widget -edit <id> or the edit script chip.
Re:T-UI Lua Focus widget preview rendered in the module dock
A Lua widget renders live state into the module dock and exposes widget-specific action chips.
Re:T-UI Lua widget config panel with text, number, toggle, select, and textarea fields
The same widget opens a terminal-styled config panel generated from the Lua schema.
Command surface

The root widget command should stay quiet.

command id: widget

The widget command is for managing Lua widgets, not for dumping every possible maintenance action into the suggestion row. Common actions should be obvious; advanced recovery commands should still exist but appear once the user narrows the command.

Command Use it for Surface rule
widget -ls List saved widgets, ids, metadata, state, and dock registration. High-intent root chip.
widget -add <name> Create a widget and open the editor with starter Lua. High-intent root chip.
widget -show <id> Open a widget as the active dock panel. High-intent root chip.
widget -config <id> Open the widget config form when the script declares one. High-intent root chip; active widgets expose this as edit.
widget -edit <id> Open the Lua source editor. High-intent root chip; active widgets expose this as edit script.
widget -check <id> Load and render once, reporting metadata, schema, or runtime errors. Advanced chip after narrowing, also shown on error surfaces.
widget -refresh <id> Re-render a saved widget. Usually a widget-specific chip, not root noise.
widget -rm <id> Delete a widget after confirmation. Advanced chip after narrowing.
$ widget -add focus_sprint
$ widget -check focus_sprint
$ widget -show focus_sprint
$ widget -config focus_sprint
$ widget -edit focus_sprint
First widget

Start with a tiny durable script.

A good first widget should render a small body, expose one or two actions, and keep state in prefs. That proves the lifecycle without dragging in networking, files, or complex config.

1

Create

Run widget -add counter, paste the script, then Save/Run.

2

Validate

Run widget -check counter before treating it as a daily tool.

3

Open

Run widget -show counter. Button chips call on_click(index).

Counter widget

-- name = "Counter"
-- type = "widget"
-- retui = "1"

local prefs = require "prefs"

local function render()
    if prefs.count == nil then prefs.count = 0 end
    ui:set_title("Counter")
    ui:show_kv({ count = prefs.count })
    ui:show_buttons({"+1", "Reset"})
end

function on_resume()
    render()
end

function on_click(index)
    if index == 1 then prefs.count = prefs.count + 1 end
    if index == 2 then prefs.count = 0 end
    render()
end
Config surface

Lua declares the form; Re:T-UI owns the UI.

entry point: on_config

Use config for user-editable values such as task names, intervals, units, display preferences, note bodies, todo text, and widget modes. Runtime buttons still belong in the live preview surface.

Declare fields

function on_config()
    ui:show_config({
        title = "Focus Settings",
        fields = {
            { id = "task", label = "Task", type = "text", value = prefs.task, required = true },
            { id = "minutes", label = "Sprint length", type = "number", value = prefs.minutes, min = 1, max = 180 },
            { id = "breaks", label = "Break reminders", type = "toggle", value = prefs.breaks },
            { id = "mode", label = "Mode", type = "select", value = prefs.mode, options = {
                { value = "focus", label = "Focus" },
                { value = "study", label = "Study" },
                { value = "writing", label = "Writing" },
            } },
            { id = "notes", label = "Notes", type = "textarea", value = prefs.notes or "", lines = 6 },
        }
    })
end

Save intentionally

function on_config_submit(action, values)
    if action == "save" then
        prefs.task = values.task
        prefs.minutes = values.minutes
        prefs.breaks = values.breaks
        prefs.mode = values.mode
        prefs.notes = values.notes
    end
    render()
end

Cancel and back should not mutate widget state. Save mutates only through on_config_submit(action, values), then the widget renders again.

Text

Single-line string input for labels, queries, names, and short values.

Number

Integer or decimal input with optional min and max.

Toggle

Boolean on/off state submitted as a Lua boolean.

Select

One string value from a declared option list.

Textarea

Multiline string input. Newlines are preserved.

Scroll

The panel grows toward the top margin and scrolls fields when there are many of them.

Persistence

Use prefs for settings, files for user content.

Widgets live under Re:T-UI's local widgets/<id>/ folder and are included in personal backup/restore. Keep small settings in prefs. Put larger notes, logs, JSON blobs, and user-generated text in widget-local files.

Prefs: good for flags, counters, selected mode, last city, sprint length, compact state.
Files: good for note bodies, todo archives, logs, cached JSON, or text that may grow.
Boundary: file names are local to the widget and cannot include path separators.

Notes widget storage

local prefs = require "prefs"
local files = require "files"

function save_notes(text)
    prefs.last_saved = os.time()
    files:write("notes.txt", text)
end

function load_notes()
    if files:exists("notes.txt") then
        return files:read("notes.txt")
    end
    return ""
end
Suggestion scripts

Not every Lua script needs to become a dock widget.

Lua suggestion scripts use -- type = "suggest". They are installed like widgets, but they do not render into the dock. They add command chips while the user types at the root prompt.

Suggestion script

-- name = "Quick Config"
-- type = "suggest"
-- retui = "1"

local strings = require "strings"
local fmt = require "fmt"

function on_suggest(query)
    local q = strings.trim(fmt.lower(query))
    if strings.starts_with(q, "cfg") then
        suggest:command("Open settings", "settings")
        suggest:command("Edit behavior", "config -file behavior.xml")
        suggest:command("Theme colors", "settings appearance theme")
    end
end

Use this when

Good fit: command shortcuts, context-aware chips, search helpers, aliases with logic.
Poor fit: visible panels, timers, status cards, note editors, or anything needing config UI.
Rule: if users need to inspect state, make a widget. If users need command hints, make a suggestion script.
Lua API map

Use the smallest API surface that solves the widget.

Lifecycle

on_load, on_resume, on_click(index), on_action(value), on_dialog_action(index), on_tick(n), on_alarm, on_config, on_config_submit.

Render

ui:set_title, ui:show_text, ui:show_lines, ui:show_kv, ui:show_table, ui:show_buttons, ui:show_action, ui:show_command.

Panels

ui:set_expandable, ui:is_expanded, ui:expand, ui:collapse, ui:toggle, ui:show_progress_bar, ui:set_progress.

Storage

prefs for small persistent values. files for widget-local text files. json for encoding and decoding structured data.

Helpers

date, fmt, strings, colors, debug, system, and aio cover common launcher-safe work.

Network

http:get, http:post, http:put, and http:delete return through network callbacks and require declared permission.

Safety and limits

Lua is launcher-native, not a general shell escape.

api version: retui 1

The runtime uses a Re:T-UI-owned Lua environment. Scripts can use safe Lua basics plus the widget APIs, not arbitrary Java, shell execution, package loading, or global filesystem access. Shell automation belongs in Termux modules.

Permission What it allows Notes
network HTTP requests through the Lua http helper. Responses are size-limited to protect launcher memory.
clipboard Read or write clipboard helpers exposed by Re:T-UI. Use only when the widget clearly needs clipboard behavior.
vibrate Trigger launcher vibration feedback. Keep it sparse; widgets should not become noisy.
local-files Use widget-local files through files. No global filesystem access.
active-tick Run on_tick(n) while the widget is the active open module. Intervals are clamped; use this for visible live state only.
-- permissions = "network,clipboard,local-files,active-tick,vibrate"
-- retui = "1"

Reality check: widgets should stay small. If a script needs background daemons, Linux CLIs, Android shell access, or heavy parsing, build a Termux-backed module and let the widget surface remain light.