Friends

{ "uses": ["friends"] }
tinylib.data.set('score', { day, seconds }, { audience: 'friends' })

const [friends, scores] = await Promise.all([tinylib.friends.list(), tinylib.friends.data.get('score')])
const board = friends
  .map((friend) => ({ name: friend.name, score: scores.get(friend.id)?.value }))
  .filter((row) => row.score?.day === day)

tinylib.friends.list() gives the persona's friends who have the app, each { id, name }. tinylib.friends.data.get(key) gives one key across every friend, as a Map of friend's id to { value, changedAt }. Only values saved for friends are there, whether the friend's page or the rules wrote them. Both calls need a connection and reject with offline without one. Friends' values are read again when the page wants them; nothing calls back when they change.

One friend

const store = tinylib.friends.data.of(friend.id)
const recent = await store.entries('day/')

of reads one friend's values with tinylib.data's verbs, each async. It's empty for anyone who isn't a friend with the app.

Sending the app

if (friends.length === 0) await tinylib.friends.sendApp()

sendApp opens Tinylib's Send sheet, where the person picks friends to send the app to. It resolves when the sheet closes, and the app never learns who was picked.

Names, blocking and reporting

blockButton.onclick = async () => {
  if (await tinylib.block(friend.id)) showBoard()
}
reportButton.onclick = () => tinylib.report(friend.id)

An id is named by the list it came from: a friend by friends.list(), a room's member by rooms.list() or ctx.members. Those lists carry current names and leave out people who are blocked or whose persona ended. block and report open Tinylib's own sheets and resolve true if the person went through with it, false if they backed out. Blocking a friend ends the friendship.