A complete, real-time, offline-capable, multiplayer shared list with presence.
Save it as index.html next to the bundle, serve the folder, open the
share link in a second tab. That is the ship requirement met, in 60 lines.
index.htmlcomplete
<!DOCTYPE html>
<meta charset="utf-8">
<title>shared list</title>
<p id="who"></p>
<form id="add">
<input id="text" placeholder="add an item" autocomplete="off">
<button>add</button>
</form>
<ul id="items"></ul>
<p>share: <a id="share"></a></p>
<script type="module">
import { openSpace } from "./automerge-kiss.bundle.js";
const space = await openSpace({
starter: { items: [] },
remember: "shared-list",
});
const share = document.getElementById("share");
share.href = space.shareUrl;
share.textContent = space.shareUrl;
// one render path — used for the first paint and every update after
space.onChange((data) => {
document.getElementById("items").replaceChildren(
...data.items.map((item, i) => {
const li = document.createElement("li");
const box = document.createElement("input");
box.type = "checkbox";
box.checked = item.done;
box.onchange = () => space.update((d) => {
d.items[i].done = box.checked;
});
li.append(box, document.createTextNode(" " + item.text));
return li;
})
);
});
document.getElementById("add").onsubmit = (event) => {
event.preventDefault();
const input = document.getElementById("text");
const text = input.value.trim();
if (!text) return;
space.update((d) => { d.items.push({ text, done: false }); });
input.value = "";
};
// presence: not saved, just who is here right now
const me = { name: "guest-" + Math.floor(Math.random() * 999) };
space.setPresence(me);
space.onPeers((peers) => {
const names = [me, ...peers.map((p) => p.presence)]
.map((p) => (p && p.name) || "anon");
document.getElementById("who").textContent =
names.length + " here: " + names.join(", ");
});
</script>
Now break it on purpose, because that is the interesting part. Turn off your
wifi, add three items, turn it back on. Nothing is lost, and neither is anything
the other tab did while you were gone.