Quickstart.
From an empty folder to an application running on your desk, registered as a device and deployed from GitHub. Fifteen minutes, no hardware required — the folder on your laptop is a device. All it asks of you is basic HTML and JavaScript.
What you need
- A computer — Linux, Windows or macOS — and a text editor.
- Basic HTML and JavaScript. If you can make a web page with a button that does something, you know enough for everything on this page. No Linux, networking or device experience is needed.
- A portal login (sign up) — to download
ardaplayerin step 1, and again from step 5, when the folder becomes a device. Steps 2 to 4 run on your computer with no network at all. - A GitHub account, for step 6.
No device is needed: the folder on your computer acts as one until you have real hardware. If you already use a toolchain — Vite, esbuild, a Makefile — it works the same way: the player serves the finished files, not the tooling. A folder of hand-written files is just as good.
1 · Get ardaplayer
ardaplayer is one program — the same one the devices run — with nothing else to install. Sign in to the portal, open Downloads, and pick the build for your computer: Linux desktop, Windows or macOS (the device builds and the OS images on the same page are for later). Put it somewhere your terminal can find it — a folder on your PATH. On Linux:
$ install -m 755 ardaplayer-linux-amd64-desktop ~/.local/bin/ardaplayer
$ ardaplayer -version
ardaplayer v1.2.3 linux/amd64
On Linux the desktop build opens the app in its own WebKit window, the same engine the devices draw with. The Windows and macOS builds open it in whatever Chrome-family browser is installed, or the system browser.
2 · Make a folder
An application is a folder with app/ inside it, and index.html at the root of app/. That is the whole contract — the player never writes into app/, and it is exactly what gets packed later. Make the two folders in a terminal as below, or in your file manager; then save this page as hello/app/index.html:
$ mkdir -p hello/app && cd hello
<!doctype html>
<html><body>
<h1>Hello from <span id="who">…</span></h1>
<p id="phase"></p>
<script>
// window.Arda exists before this line runs — no import, no wait
Arda.device().then(function (d) {
document.getElementById('who').textContent = d.hostname
document.getElementById('phase').textContent = 'phase ' + d.phase
})
// the device's state moved: registered, locked, resyncing — ask again
Arda.on('device', function () { location.reload() })
</script>
</body></html>
3 · Run it
In a terminal, inside the hello folder:
$ ardaplayer -dev .
ardaplayer v1.2.3 — ArdaForm device runtime
device local — no control plane is dialled; the app runs on its own stores.
level=INFO msg="serving app" … dev=true
A window opens with your page in it. The phase reads local: nothing is connected and nothing is registered anywhere yet, so you can build with no network at all. The inspector is a right-click away; nothing reloads under your hands unless you say so. Say so in arda.dev.json, beside app/, not inside it:
{
"window": "1280x800",
"console": true,
"watch": true,
"can": { "touch": true, "keyboard": false, "mouse": false }
}
can is what your app will be told the machine has, so a touchscreen layout can be built on a desk with a mouse — Arda.device() resolves with exactly this as its can. A flag on the command line always wins over the file, an unknown key is refused rather than ignored, and the file is never packed: outside developer mode the player refuses to start if it finds one.
4 · Data and settings
Most apps need some data — a member list, a price list — and a few settings somebody can change without touching the code — the kiosk's name, the currency. Both are declared in app/manifest.json: the data as datasets, the settings as fields an operator fills in. You decide what exists; the operator fills in the values in the portal.
{
"datasets": [
{ "key": "members", "access": "ro", "api": "rw", "needForBoot": true },
{ "key": "visits", "retention": { "days": 400 } }
],
"settings": [
{ "key": "kiosk_name", "caption": "Kiosk name", "type": "text", "perDevice": true },
{ "key": "currency", "caption": "Currency", "type": "select",
"options": ["GBP", "EUR"], "default": "GBP" }
]
}
members is filled through the HTTP API and read-only on devices; visits is what the kiosk writes. needForBoot says the app should not start until that dataset has arrived; everything else syncs behind it. Reading both from the page:
// A frozen object, readable on the first line. Absent is absent, so ?? is right.
var name = Arda.config.kiosk_name ?? 'Reception'
// Asking IS subscribing: the first call may answer null (nothing yet) and a 'sync' follows.
function draw() {
Arda.db.dataset('members').then(function (rows) { render(Object.values(rows || {})) })
}
draw()
Arda.on('sync', function (dataset) { if (dataset === 'members') draw() })
// The only way up: a durable outbox, delivered at least once, survives power loss.
Arda.db.push('visits', { member: id, at: new Date().toISOString() })
Locally, the player builds a store for every dataset in the manifest, reads back what you push straight away, and loads seed/<key>.json beside app/ into an empty store — so seed/members.json with a few records gives you data to render before a server exists. Settings come from the manifest's defaults plus a "settings": {…} block in arda.dev.json.
5 · Turn the device on
One line in arda.dev.json makes the folder a real device: it registers with ArdaForm, receives its data and settings, and appears in the portal exactly as a till or a Pi would.
{ "window": "1280x800", "console": true, "watch": true, "device": true }
$ ardaplayer -dev .
ardaplayer v1.2.3 — ArdaForm device runtime
ACTIVATION CODE K7X4P — enter in the portal: Devices → Add device
The window shows the same code. In the portal open the project, Devices, Add device, type it, and the phase moves to ready — settings arrive with it, a value changed in the portal later reaches the device in about three seconds, and your 'config' listener fires. Each app folder is its own device: its identity lives under hello/state/, so two folders are two rows in the portal with separate data. Keep state/, data/ and storage/ out of git; arda.dev.json may be committed because it is never packed. Records you pushed while local travel up the moment the device is on.
6 · Deploy from GitHub
There is no build to upload. Devices run what a branch of your repository holds, packed and sealed by ArdaForm when it deploys — nothing from the repository is ever executed on the way.
- Push the folder to a GitHub repository (the
app/folder can sit at the root or in a subdirectory). - Install the GitHub App at github.com/apps/ardaform on the account or organisation that owns the repository — until it is installed the portal can see nothing.
- In the project's Connectors tab press Connect GitHub, then add a connection with +: a project branch, the repository, the folder whose
index.htmlis the app's root (hereapp), and the git branch to follow. A project branch reads one git ref; a device follows one project branch. - Push a commit. ArdaForm reads the head, packs it, and every device on that branch is told within seconds.
$ git add -A && git commit -m "first kiosk screen" && git push
…
portal › Connectors: main reads main · a1f3c92 · 2 s ago · deployed
The Connectors tab shows the commit each branch has read and whether its devices run it; the Code tab shows each branch's latest push and its apply policy. The apply policy is set on the branch: as soon as updates arrive, only inside a time window in the device's own local time, or only when the app asks for them.
7 · Watch it run
Open the device from the Devices tab. Device view shows the screen on demand or every few seconds, the app's console as it happens, and evaluates an expression inside the running page. Alerts & log switches log capture on for a while and ships the last few hundred lines the device already kept, so you read what happened rather than waiting for it to happen again. The Screenshot button above the tabs captures every connected screen at full resolution as a lossless PNG.
> Arda.config
{ kiosk_name: 'Front desk', currency: 'GBP' }
> Arda.db.status()
{ ready: true, datasets: { members: { synced: true, boot: true, records: 412 }, visits: {…} }, files: null }
8 · Handle updates
An update is downloaded, verified and staged; the app picks the moment. Say nothing and it applies five seconds later. Ask for time and you get sixty seconds, up to five times.
Arda.on('updateStart', function (u) {
// u.version, u.mode, u.urgent, u.ask, u.ofAsks, u.decideInMs, u.pauseForMs
if (!midCheckIn()) return // silence: it applies in 5 s
Arda.update.pause() // not yet — buys u.pauseForMs
afterCheckIn(function () { Arda.update.apply() })
})
Applying is a page reload onto the new version. A restart applies whatever is staged with no negotiation — except on a branch set to apply only when the app asks — and an operator can apply from the device screen when the app's answer is wrong.
Where next
- Developer manual — every area an app can reach, one page each, with checked examples; read the order of operations before turning a folder into a device.
- Deployment guide — images, the USB stick, adopting devices, install codes, profiles, the device screen, player updates and decommissioning, with pictures.
- Native APIs — the rest of
window.Arda: the on-screen keyboard, screens and audio levels, cameras, printers, serial, Wi-Fi, webhooks. - Package format — what ArdaForm packs, what it refuses, and how a device verifies it.