Every handler receives host; the global perch is the same table —
capability namespaces (perch.time, perch.media…) and host verbs live
side by side on it. Use whichever spelling reads better.
host.set_presence(widget, present, data)
host.set_presence("worldclock", true, { city = "Tokyo", time = "17:42" })
host.set_presence("worldclock", false)
Declares that a widget exists (or stops existing) and delivers its payload in one call. The arbitration engine takes it from there — placement, entrance, exit, and every negotiation with other widgets are not your concern.
- For widgets with a
transientcontext, presence auto-retracts after the context’stimeout_ms; a freshset_presence/push_dataextends the moment. - Call it
falsefromon_stopif your data becomes meaningless when the mod stops; otherwise the runtime retracts for you.
host.push_data(widget, data)
host.push_data("worldclock", { time = "17:43" })
The steady-state channel. The payload is a table serialized to JSON; nodes bound to changed fields repaint, and nothing else happens.
Partial payloads are the intended style — push what changed. Merging is by key at every level that matters:
- Melt: sustained pushes are admitted at ~5/s per mod (burst of 20). An over-cap push parks; parked payloads merge per key (newest wins) and deliver as the window drains. The final state of a burst always reaches the view — coalesced, never dropped.
- A parked art-only push survives a later art-less push — disjoint keys coexist.
host.timer(name, ms, once?)
host.timer("poll", 60 * 1000) -- repeating, floored at 1000 ms
host.timer("settle", 250, true) -- one-shot, floored at 100 ms
host.cancel_timer("poll")
Named timers firing on_timer(host, name). Re-arming a name replaces it;
cancel_timer removes it. Floors are enforced (1000 ms repeating / 100 ms
one-shot) — they are the idle-cost contract, not a suggestion.
host.log(msg)
host.log("fetch ok: " .. #items .. " items")
Writes to the Perch log as perch: mod[<id>]: <msg>. Visible with the app
logs; pairs with PERCH_LOG=mods=debug for the loader’s own tracing.
What host is not
- There is no
request_quit, no window handle, no DOM access, no way to reach another mod. The host surface is deliberately exactly what a well-behaved widget needs. - Handlers all run on the main thread; host calls are non-blocking, always.