# CausalBuffer

Import it from `@grafloria/engine`.

Holds ops whose entity has not been added yet, and releases them the instant it is.

Sits BETWEEN the transport and the Replica. Nothing it holds has touched the log, the
clock or the LWW registry — which is the whole point: an op the replica has never seen
is an op that can still be applied later.

```ts
class CausalBuffer
```

**Properties**

| Name | Type | Default | Description |
| --- | --- | --- | --- |
| `overflowed` |  | `0` | Ops force-released because the buffer was full. Should be 0. Watch it. |

**Methods**

- `constructor( private readonly diagram: DiagramModel, options: CausalBufferOptions = {} )`
- `get pendingCount(): number`
- `noteLocal(op: Op): void` — WE created an entity. Record it — the transport will never tell us about it.

THE FUZZ FOUND THIS ONE, AND IT IS A PERMANENT HANG, NOT A HICCUP.

`known` was fed from exactly two places: entities on the diagram at construction, and
`add` ops that arrived from PEERS. A node the local user creates during the session is
in NEITHER — so as far as the buffer was concerned, an entity this peer invented did
not exist.

Now watch a peer edit it. Bob creates node X. Alice learns about it, drags it, and
sends `set(X, position)`. It arrives at Bob, whose buffer asks "have I seen an `add`
for X?", answers NO, and holds it — waiting for an `add(X)` that is never coming, from
anyone, ever. It cannot come: the only `add(X)` in existence is Bob's OWN, it is
already in Bob's log, and Alice's anti-entropy correctly concludes that Bob already
has it and never echoes it back.

So Bob's node freezes wherever it was when he made it, Alice watches it move, and the
two documents differ forever — over the single most ordinary interaction in a
collaborative editor: one person moving another person's node. On a healthy transport
it never fires (the `set` cannot overtake an `add` that was never sent), which is
exactly why it took a reordering channel to find it.

No drain is needed. A peer can only `set` an entity it has learned about, which means
our `add` had already reached it — so nothing can be waiting on an id at the moment we
create it. The registration alone closes the hole.
- `admit(incoming: readonly Op[]): CausalSplit` — Split an arriving batch into what can be applied now and what must wait — and fold in
anything that was already waiting and has just become releasable.
- `pending(): Op[]` — Everything we are still holding — for a status panel, and for the tests.
