Start.
From an empty folder to an app that reads a setting, lists records and writes new ones — on your own computer, with no network and nothing registered. Ten minutes. You need ardaplayer (the portal's Downloads page, as in quickstart step 1) and a text editor.
1 · The folder
An app is a folder with app/ inside it and index.html at the root of app/. Everything else on this page is optional. Make this layout:
counter/
app/
index.html the app
app.js
manifest.json its settings and datasets
seed/
items.json fake data for your desk (never packed)
arda.dev.json how the player runs on your desk (never packed)
2 · Declare a setting and two datasets
{
"datasets": [
{ "key": "items", "access": "ro", "api": "rw", "needForBoot": true },
{ "key": "taps" }
],
"settings": [
{ "key": "title", "caption": "Screen title", "type": "text", "default": "Tap an item", "perDevice": true },
{ "key": "show_counts", "caption": "Show counts", "type": "bool", "default": true }
]
}
items is filled from outside (the portal or the HTTP API) and is read-only to devices; the app waits for it before it starts. taps is what devices write. title may differ per device. The manifest page has every key.
3 · Seed data and desk settings
In a local run the player fills an empty dataset from seed/<dataset>.json, so you have something to draw before any server holds data. Each record carries its key:
[
{ "key": "tea", "name": "Tea", "colour": "#2f6f4f" },
{ "key": "coffee", "name": "Coffee", "colour": "#6f4a2f" },
{ "key": "cake", "name": "Cake", "colour": "#8a3b5c" }
]
arda.dev.json sits beside app/, never inside it. Its settings stand in for the values an operator would type in the portal:
{
"window": "1024x600",
"console": true,
"watch": true,
"settings": { "title": "Front desk" }
}
4 · The page
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Counter</title>
<style>
body { margin: 0; font: 20px system-ui, sans-serif; background: #111; color: #eee; }
h1 { margin: 24px; font-size: 28px; }
#items { display: flex; flex-wrap: wrap; gap: 16px; margin: 24px; }
button { min-width: 160px; min-height: 96px; border: 0; border-radius: 12px; color: #fff; font: inherit; }
</style>
</head>
<body>
<h1 id="title"></h1>
<div id="items"></div>
<script src="app.js"></script>
</body>
</html>
// Arda exists before this line runs. Arda.config is a plain frozen object.
function title() { return Arda.config.title ?? 'Tap an item'; }
function showCounts() { return Arda.config.show_counts ?? true; }
// A record synced from ArdaForm is an envelope {key, data, deviceId, seq, createdAt, updatedAt};
// a seed record (and a record you pushed, read back on a desk) is stored as written.
// fields() gives the record's own fields either way.
function fields(r) { return (r && r.seq !== undefined && r.data !== undefined) ? r.data : r; }
var counts = {};
function draw() {
document.getElementById('title').textContent = title();
// dataset() can resolve null before anything has arrived: treat that as empty
Arda.db.dataset('items').then(function (rows) {
var box = document.getElementById('items');
box.textContent = '';
Object.keys(rows || {}).forEach(function (key) {
var item = fields(rows[key]);
var b = document.createElement('button');
b.style.background = item.colour || '#333';
b.textContent = item.name + (showCounts() ? ' · ' + (counts[key] || 0) : '');
b.onclick = function () { tap(key); };
box.appendChild(b);
});
});
}
function tap(key) {
counts[key] = (counts[key] || 0) + 1;
draw();
// The one way up: a durable outbox. Give every record a key of your own.
var id = key + '-' + Date.now();
Arda.db.push('taps', { item: key, at: new Date().toISOString() }, id)
.then(function (r) { console.log('queued tap', id, 'as seq', r.seq); },
function (e) { console.error('could not record the tap:', e.message); });
}
draw();
Arda.on('sync', function (name) { if (name === 'items') draw(); }); // more data arrived
Arda.on('config', draw); // a setting changed
5 · Run it
$ ardaplayer -dev .
level=INFO msg="seeded a local dataset" dataset=items records=3
level=INFO msg="local mode: no control plane will be dialled"
A window opens with three buttons and the title Front desk. Tap one: the count moves and the console says the tap was queued. Change "title" in arda.dev.json and restart, or edit app.js and the page reloads (that is "watch": true). Nothing has left your computer: the phase is local, and queued records wait in the outbox until the folder becomes a real device.
6 · Where next
- Make it a real device —
"device": trueinarda.dev.json; it shows an activation code you enter in the portal. Before that, read the order of operations: the datasets have to exist on ArdaForm before records can land in them. - Put it on hardware — push the folder to GitHub, connect it in the portal and install a device image: quickstart steps 6–8 and the deployment guide.
- Understand the data — Datasets and sync explains what a device sees and when.
Checked: the manifest above passes ArdaForm's own pack-time check (the plane's lib/manifest.js parser) and the player's parser; the seed format, local echo of a keyed push, dataset() answering null for an empty store, and the config values were observed running ardaplayer (built from source: v1.1.16 and ten unreleased commits) on these files. The window itself (DOM drawing) was not driven by a test here.