# Awareness

Import it from `@grafloria/engine`.

The presence store for one peer: our own state, and everyone else's.

Deliberately transport-free. `SyncAdapter` wires it to a channel; a test wires it to
nothing and drives it directly. It never sees an Op, a Replica or an OpLog, and that
is not an accident — it is the containment.

---------------------------------------------------------------------------
WHY `lastSeen` IS A WALL CLOCK WHEN THE LOG WENT TO SUCH LENGTHS TO AVOID ONE
---------------------------------------------------------------------------
Because the question is different. The log asks "did A happen before B?", which a wall
clock cannot answer across machines (they disagree, and they run backwards). Expiry
asks "has it been 15 seconds since I last heard from Bob?" — measured entirely on MY
clock, about MY receipts, comparing my own `now()` to my own earlier `now()`. Bob's
clock is never consulted, so its wrongness cannot infect anything. Using a Lamport
clock for a TIMEOUT would be the actual mistake: it has no relationship to seconds.

```ts
class Awareness
```

**Methods**

- `constructor(private readonly options: AwarenessOptions)`
- `get actor(): ActorId`
- `getLocalState(): AwarenessState` — Our own published state, and the sequence a peer will LWW it by.
- `get localSeq(): number`
- `setLocalState(patch: Partial<AwarenessState>): boolean` — Merge a patch into our own state and bump the sequence.

Returns false when nothing actually changed — a mouse that has not moved must not
burn a sequence number, wake the throttle, or send a message. (A `mousemove` fires
on sub-pixel jitter and on scroll; without this guard an idle user with a resting
hand is a 60Hz broadcaster.)
- `applyRemote(actor: ActorId, state: AwarenessState | null, seq: number): AwarenessChange | null` — Take a peer's published state.

`seq` is the whole reason a reordered transport does not make a cursor jump
backwards: a sample older than the one we already hold is REFUSED, exactly as the LWW
gate refuses a superseded op. Same idea, one scope down, and no shared machinery
because the lifecycles have nothing in common.
- `remove(actor: ActorId): AwarenessChange | null` — An explicit `bye`, or a transport that told us the peer is gone.
- `prune(): AwarenessChange | null` — Drop peers we have not heard from inside the timeout.

THE GUARANTEE, as opposed to `bye`, which is merely the optimisation. A crashed tab,
a closed laptop, a killed process and a severed cable all send no `bye` whatsoever,
and every one of them must eventually stop showing a cursor.
- `getPeers(): PeerPresence[]` — Everyone but us.
- `getPeer(actor: ActorId): PeerPresence | undefined`
- `get peerCount(): number`
- `clearPeers(): AwarenessChange | null` — Everyone is gone (we disconnected — we can no longer vouch for anyone).
- `onChange(listener: (change: AwarenessChange) => void): () => void`
