Skip to content

The Long Mural

A single canvas, endlessly wide, that anyone can draw on and that never resets. People who never meet add strokes side by side over months — a stretch of city drawings gives way to a field of flowers, then a wall of names. Whatever you add is still there when you come back.

The piece leans on three Starfish features:

Shared state — strokes that last and merge. The canvas is split into tiles, each a key in session state. Adding a stroke appends to that tile, so two people drawing at once never overwrite each other.

ts
await client.join("the-long-mural");

function commitStroke(tileKey, stroke) {
  client.state.set({ key: tileKey, scope: "session", op: "list.add", value: [stroke] });
}

// Watch the tiles on screen and redraw as strokes arrive.
client.state.keyChanges$(tileKey).subscribe((result) => redrawTile(tileKey, result.value));

A running total of every stroke ever made is just as safe to share, using a counter that stays correct even under simultaneous edits.

ts
client.state.set({ key: "stroke-count", scope: "session", op: "counter.add", value: 1 });
client.state.keyChanges$("stroke-count").subscribe((result) => showCounter(result.value));

Scopes — shared vs. private. The session scope is the shared canvas everyone sees. The self scope is private to you and follows you between visits — a natural fit for your brush settings.

ts
client.state.set({
  key: "my-brush",
  scope: "self",
  op: "replace",
  value: { color: "#c94f7c", width: 3 },
});

Optimistic concurrency — safe overwrites. Most drawing only adds, but some actions replace — like a moderator clearing a defaced tile. A version check makes sure that only succeeds if nobody has touched the tile in the meantime.

ts
const tile = await client.state.get({ key: "tile:0042:0017", scope: "session" });

await client.state.set({
  key: "tile:0042:0017",
  scope: "session",
  op: "replace",
  value: [],
  expectedVersion: tile.version, // rejected if the tile changed since we read it
});

Everyone paints at once, nothing is lost, and the mural just keeps growing.