# `Leaf.Collab`
[🔗](https://github.com/alexdont/leaf/blob/main/lib/leaf/collab.ex#L1)

Live editing, wired into a LiveView in one call.

Everything a shared document needs — placing edits that crossed on the wire,
agreeing which version everyone is on, putting a session back in step after
its socket drops, showing where other people's carets are — is here rather
than in the host. It is not obvious code and it is not code worth writing
twice; every part of it exists because something went visibly wrong without
it.

## Using it

    def mount(%{"id" => id}, _session, socket) do
      {:ok,
       Leaf.Collab.join(socket,
         room: MyApp.Notes.room_name(id),
         editor_id: "note-editor",
         identity: %{name: socket.assigns.current_user.name}
       )}
    end

and in the template:

    <.leaf_editor
      id="note-editor"
      content={@leaf_collab.content}
      collaboration={@leaf_collab.collaboration}
    />

That is the whole integration. `join/2` attaches a `handle_info` hook, so the
host writes no message handling of its own. A LiveView that switches
documents without remounting calls `leave/1` before joining the next room —
see `leave/1`.

## What the hook consumes

The hook handles — and stops — `{:leaf_operation, …}`, `{:leaf_awareness, …}`,
`{:leaf_ready, …}`, `{:leaf_resync, …}`, `{:leaf_debug_state, …}`,
`{:leaf_changed, …}`, and the room's `{:leaf_conflict, …}` /
`{:leaf_conflict_cleared, …}`. A collaborating LiveView therefore does **not** receive
`{:leaf_changed, …}`: read the document from `@leaf_collab.content`, which is
kept current on every edit, local or remote. Anything the hook does not
recognise passes through to the host's own `handle_info` clauses untouched.

One collaborative editor per LiveView: `join/2` owns the `@leaf_collab`
assign and the hook name.

## What the host still owns

Starting a room per document and supervising it — Leaf has no opinion about
how many nodes you run or how you name processes. And saying where documents
live, which is `Leaf.Collab.Store`.

## Turning it off

Not calling `join/2` costs nothing. The editor does no collaboration work
unless it is asked to: no coordinates measured, no fingerprints taken, no
selection listener attached. Somebody using Leaf for a comment box pays for
none of this.

# `join`

Join the document this room holds.

Options:

  * `:room` — the room process, started and supervised by the host
  * `:editor_id` — the `id` given to `leaf_editor`
  * `:identity` — `%{name:, color:}`, both optional. A host with signed-in
    users passes theirs so everyone sees a name rather than an identifier.
  * `:awareness` — show other people's carets and selections. Defaults true.
  * `:debug` — diagnostics. Defaults false; see `Leaf.Collab.Log`.

# `leave`

Leave the document, undoing what `join/2` did.

For a LiveView that outlives its stay in a room — one that switches
documents without remounting, the way an editor with a sidebar keeps the
sidebar alive. Closing the tab needs no call: the room monitors the
process. But a live process that merely moves on is invisible to the
monitor, so without this the person lingers as a ghost caret in the
document they left, and the next `join/2` raises on the hook it cannot
attach twice.

Removes this session from the room so its caret disappears for everyone
else, unsubscribes, detaches the hook, and clears `@leaf_collab` — after
which `join/2` works again with whatever room comes next:

    def handle_params(%{"id" => id}, _uri, socket) do
      {:noreply,
       socket
       |> Leaf.Collab.leave()
       |> Leaf.Collab.join(room: room_for(id), editor_id: "note-editor")}
    end

Idempotent: leaving without having joined is a no-op, so the pipeline
above needs no first-mount special case.

# `people`

Everyone in the document, for a host that wants to list them.

# `reset`

Put the document back to its starting text, for everyone.

Handles the part a host would forget: telling the other sessions. A reset
that only changed the room left every other tab holding the old text, and
the next reconciliation adopted it straight back — the reset undone by the
people it was for.

---

*Consult [api-reference.md](api-reference.md) for complete listing*
