HTML embeds and apps
HTML runs in two places: html blocks inside a message, and apps, which are full-page views under a channel. Both run your HTML in a sandboxed frame and give it the same rply object.
rply send "Traffic this week" --blocks '[{"type":"html","label":"Traffic","html":"<div id=chart></div><script>…</script>"}]'rply app set dashboard dashboard.html --title DashboardThe HTML can be a fragment or a full document; the outer <!doctype> and <html> tags are dropped.
The sandbox
Section titled “The sandbox”- The frame can run scripts, but it can’t read the app, its storage, or the person’s session.
localStorageand cookies aren’t available. Userply.storageinstead.- Links to
http(s)addresses open in a new tab (on a phone, in the browser), never inside the frame. - Embeds grow to fit their content. Set
heighton the block to fix it instead. Apps fill the page.
The rply object
Section titled “The rply object”rply.send(value, label?)
Section titled “rply.send(value, label?)”Sends an event to the agent, any number of times, for example when someone clicks a bar in a chart. It arrives in rply inbox:
event "Show Tuesday" from chart in message pweehax515jh7zv2: {"day":"tue"}The app shows it as a short “Sent: Show Tuesday” line. Events never answer a question.
rply.reply(value)
Section titled “rply.reply(value)”Answers the question, when the block is marked "question": true and has an id. Like any question, only the first answer counts.
{"type":"html","id":"pick","question":true,"html":"<button onclick=\"rply.reply('blue')\">Blue</button>"}rply.call(name, args) → Promise
Section titled “rply.call(name, args) → Promise”Runs the command script name on the agent’s machine with args as JSON, and resolves with its output. Requires rply serve to be running. See Build an app.
const stats = await rply.call("stats", { range: "7d" });rply.agent(name, args) → Promise
Section titled “rply.agent(name, args) → Promise”Asks the agent itself, and resolves with its answer from rply respond. Every call becomes a message, so don’t use it for polling.
Both call and agent reject with an Error whose message is offline (no rply serve running), timeout, or the error the script or agent reported.
rply.storage
Section titled “rply.storage”Small values per person and app, synced across their devices. Both methods are synchronous; values are saved in the background.
const tab = rply.storage.get("tab") ?? "week";rply.storage.set("tab", "month");Values must be JSON. Each app or message can store up to 256 KB.
rply.theme
Section titled “rply.theme”"light" or "dark", following the app’s theme rather than the system’s.
Styling
Section titled “Styling”Buttons, inputs, links and tables get default styles that match the app. The page background is transparent. Use these CSS variables, so your HTML works in both themes, instead of hard-coding colors:
| Variable | Use |
|---|---|
--rply-fg |
Text |
--rply-muted |
Secondary text |
--rply-border |
Borders and dividers |
--rply-surface |
Tinted backgrounds, e.g. buttons |
--rply-bg |
The app’s background, e.g. for sticky headers |
--rply-accent |
Links and highlights |
--rply-success, --rply-warning, --rply-danger |
Status colors |
The theme is set as data-theme="light" or "dark" on <html>. It can change while the page is open; the window then receives a rply:theme event. Redraw anything that read colors once, like a canvas chart:
addEventListener("rply:theme", () => drawChart());Errors
Section titled “Errors”Uncaught errors and rejected promises show the person a banner: “This app hit an error: …”. They can click Send to agent, which sends an event to the agent with the value { "error": "…" }.