Errors

try {
  const { id } = await tinylib.rooms.join(code)
  tinylib.places.go(`game/${id}`)
} catch (error) {
  if (error.code === 'not_found') codeField.setCustomValidity(error.message)
  else toast(error.message)
}

A call that didn't do what it asked rejects, or throws if it's synchronous, with a TinylibError: a code from ErrorCode and a message written to show the person. A call given arguments it can't take throws or rejects with a TypeError. A rejection nobody catches shows only in the console, and in tinylib dev.

Sheets resolve

if (await tinylib.block(opponent.id)) tinylib.places.go('')
await tinylib.friends.sendApp()

A call that opens one of Tinylib's sheets or cards resolves when it closes, whatever the person chose there. Backing out isn't an error: block and report resolve false, and persona.edit resolves with the switch unchanged.

Without a connection

tinylib.connection.onChange(() => (offline.hidden = tinylib.connection.online))

tinylib.data and tinylib.reminders work without a connection. Opening a room, and the calls that reach Tinylib's servers, reject with offline. An open room drops with the connection and opens again when it's back.

Errors in the rules

on(state, input, ctx) {
  if (input.type === 'action' && input.name === 'move') {
    if (!state.players.includes(input.from.id)) ctx.refuse('You are watching this game.')
  }
}

ctx.refuse is the rules saying no: the member who acted gets the sentence. A throw in the rules changes nothing and sends nothing: a request is refused with Tinylib's own sentence, any other input is dropped, and the room carries on. tinylib dev shows every input, output and error as it happens.