# Collaboration

A canvas that several people open at once, as a shared design file is:
each sees the others' changes as they happen, their cursors, the nodes
they selected and those they drag, in their colours.

A shared canvas is a room of [Issho](https://hex.pm/packages/issho),
collaboration for Phoenix: Hakushi on its own is a canvas for one person,
and Issho makes it a canvas for many, with the people's own permissions
and no service besides your app.

```heex
<.canvas
  id="schema"
  graph={@schema.graph}
  on_change={&Schemas.change(&1, &2, @schema)}
  issho={[instance: MyApp.Issho, document: "schema:#{@schema.id}"]}
  user={%{id: @current_user.id, name: @current_user.name}}
>
```

`issho` takes the keys of every component of an Issho page (`Issho.Join`,
the same for Kotoba's editor and Masu's grid): `:instance` and
`:document` (required: the canvas's document; its room is the part
before the first `"/"`, so the blocks of a page whose documents share a
room share its people); `:can`, `:read` for a viewer (as `readonly`);
`:people`, `false` to hide the canvas's avatars on a page that lists the
room's people (`Issho.Components.people/1`); `:follow`, `false` for a
canvas that takes no part in following; `:on_behalf_of` and `:claims`,
for your policy. Any other key raises.

## Setting up

1. Issho in your dependencies:

   ```elixir
   {:issho, "~> 0.4.2"}
   ```

2. An Issho instance in your supervision tree, after the PubSub, with
   your policy: who may open which canvas, and change it.

   ```elixir
   {Issho,
    name: MyApp.Issho,
    pubsub: MyApp.PubSub,
    endpoint: MyAppWeb.Endpoint,
    policy: MyApp.Collaboration}
   ```

   ```elixir
   defmodule MyApp.Collaboration do
     @behaviour Issho.Policy

     @impl true
     def authorize(%Issho.Identity{key: user_id}, "schema:" <> id, action) do
       if MyApp.Schemas.allowed?(user_id, id, action), do: :ok, else: {:error, :forbidden}
     end
   end
   ```

   `action` is `:join` (open the canvas, see who is here), `:read`
   (receive the changes), `:write` (change it), `:lock`, `:comment`
   (write in the room's threads, see [Comments](#comments)) or
   `:interrupt` (pause or stop someone else's agent). The policy is
   asked as each person opens the canvas, and again for each change, in
   both directions: a person whose access you take away stops seeing the
   changes, and `Issho.revoke(MyApp.Issho, topic, user_id)` takes them
   out of the room at once.

3. The hook, in the LiveView or its `live_session`:

   ```elixir
   live_session :default, on_mount: [Hakushi.Live] do
   ```

   (`Hakushi.Live` is Issho's hook, `Issho.Live`: a LiveView with either
   needs no other.)

## The room's document

The canvases of a topic share one document, the graph, with one process
in your cluster that puts the changes of everyone in order. It runs while
anyone has the canvas open, wherever: on its own page, embedded in a
document of another page (see [In a page](embedding.md)), or for an
agent. Nobody needs to be on the canvas's page for the others to share it.

What the document is (its graph, rules and `on_change`) comes from your
Issho policy when it says, whoever opens it first:

```elixir
defmodule MyApp.Collaboration do
  @behaviour Issho.Policy

  @impl true
  def document(_who, "schema:" <> id) do
    schema = MyApp.Schemas.get!(id)
    Hakushi.Issho.document(schema.graph, rules: [cycles: false], on_change: &MyApp.Schemas.change(schema, &1, &2))
  end
end
```

Define it there when the canvas opens in more than one place, or for
agents: it is then the same document whichever opens it first, and an
agent works on it with no browser at all. Without it, the first canvas to
open the topic gives it its `graph`, `rules` and `on_change`, the others
get the graph as it is then, and their own `rules` and `on_change` are not
used: two places that pass different ones would differ by who came first.

Each change of each person goes to that process, which checks the rules,
applies it, asks `on_change` (once, whoever made it, with the graph
before and after it), and only then sends it to every canvas of the
topic. So `on_change` runs once for each change, and two people never
save over each other: the second change applies to the graph the first
one made. A change that no longer makes sense (an edge to a node someone
deleted a moment before) is refused, with the reason.

The document lasts while a canvas has it open, and a few seconds more,
so that a reload finds it. Your tables, which `on_change` writes, are
the truth: the next canvas to open the topic starts from the graph you
give it.

## People

`user` is your user: a struct of yours for which you implement
`Issho.Actor` (its key, `"user:4"`, and what others see of it), or a map
with an `:id`, and a `:name`, a `:color` (any CSS colour) and an
`:avatar` (an image's URL) if you have them. Give the canvas the same
user as your other blocks (a Kotoba editor, Issho's people list), and
the person is one person in all of them. A person with no colour gets
one of Issho's eight (`--issho-person-1` to `-8`, the same as in Issho's
lists), else one of Hakushi's, by their key. A person with the canvas
open in two tabs is one person, and does not see themselves. Without a
`user`, a canvas joins as someone anonymous.

A read-only canvas (`readonly`, or `issho`'s `can: :read`) is a viewer of the room: Issho's lists
show them viewing, their cursor still goes to the others, and the room
takes no change from them.

The avatars of the others show in the corner of the canvas (not with
`people: false`). Draw them yourself with the `:person` slot, which gets
a map with `"id"`, `"name"`, `"color"` and `"avatar"`.

## Cursors and drags

Each person's cursor, the nodes they selected and the nodes they drag go
to the others as they move, in Issho's awareness frames: one frame of
what changed, about twenty times a second, whatever the number of people.
A node someone drags moves for everyone while they drag it, with their
name; it lands where they drop it when the room has the move.

A line someone draws with a kit's tool (ink, `Hakushi.Kits.Ink`) goes to
the others the same way, as they draw it: each canvas draws it under
their cursor, in its colour, and it becomes a change of the room's when
the pen lifts.

They are the canvas's own keys of the person's state, under the canvas's
document (`"blocks"`, see `Issho.Awareness`): `cursor`, `selected`,
`viewport`, `dragging` and `ink`. Issho's own keys (`place`, `pointer`, `view`,
`motion`) stay Issho's, so the other blocks of the page never collide
with the canvas's.

## A place on the canvas

Where someone is on the canvas, in a pointer, in an agent's motion or in
what your app keeps, is a place of the canvas's document, in the
canvas's own terms (never screen pixels, as each person has their own
pan and zoom). Its format is part of Hakushi's API, and only grows
(`Hakushi.Issho.Place`):

```elixir
%{"point" => [x, y]}          # a point, in the canvas's coordinates
%{"node" => node_id}          # a node
%{"nodes" => [node_id, ...]}  # nodes (the first is where it is drawn)
```

## A cluster

The room's process is found from every node of a cluster (`:global`,
unless your Issho instance names another registry), and so is each agent
in the room; the PubSub must reach every node, as Phoenix's does.

## Events

Each change of a shared canvas is an event of its room
(`Issho.Event`), once the room has it, named for what the change did:

| Event | When | `data` |
|---|---|---|
| `canvas.added` | nodes added: dropped, pasted, an undone delete | `%{nodes: [%{id, type}], edges: [%{id, source, target}]}` |
| `canvas.connected` | edges drawn | `%{edges: [%{id, source, target}]}` |
| `canvas.reconnected` | an edge's end moved to another node | `%{edges: [%{id, source, target}]}`, its new ends |
| `canvas.moved` | nodes moved or resized | `%{nodes: [%{id, x, y}]}`, with `width` and `height` for a resize and `parent` for a node put in a group or out; the nodes inside a moved node too |
| `canvas.updated` | nodes or edges changed: a node's data, an edge's label | `%{nodes: [id], edges: [id]}` |
| `canvas.deleted` | nodes and edges deleted | `%{nodes: [id], edges: [id]}`, with the nodes inside and the edges that went with them |

The event's `document` is the canvas's, and its `by` who made the change
(a person, an agent and whom it acts for, your server). A change of the
paper makes none of its own: Issho's `document.changed` says it, as it
says every change.

An agent hears what people do on the canvas, and acts on it:

```text
wait_for_events {"room": "schema:42", "names": ["canvas.*"]}
```

A component of the page hears them (`Issho.Live.subscribe/3`), and so
does your app, for a log, a notification, a count:

```elixir
Issho.Events.subscribe(MyApp.Issho, "schema:42")

def handle_info({Issho, :event, %Issho.Event{name: "canvas.deleted", data: data}}, socket) do
  ...
end
```

## Comments

Issho's comments (`Issho.Components.comments/1`) go on the canvas as on
any block of the page: a person selects nodes, writes, and the thread is
on them, with a mark at the node on each person's screen, where it is in
their pan and zoom. Hakushi says where its places are, so the marks need
no code of yours. Give the panel the canvas's words for a place with the
`:place` slot and `Hakushi.Issho.Place.label/3`:

```heex
<.canvas id="schema" graph={@graph} issho={[instance: MyApp.Issho, document: @doc]} user={@current_user} />

<.comments id="comments" room={@doc}>
  <:place :let={%{place: place, default: default}}>
    {Hakushi.Issho.Place.label(place, @graph, document: @doc) || default}
  </:place>
</.comments>
```

A thread on a node says `The node “customers”` (its name as the canvas
says it to a screen reader: give `label/3` the canvas's `kits` and
`node_label` if it has them), on several nodes `The node “customers” and
2 more`, on a point `A point on the canvas`, and the panel's own words
when the nodes are gone.

The graph is the room's, as it is now: keep it with the canvas's events,
in the LiveView that is in the room.

```elixir
def mount(_params, _session, socket) do
  doc = "schema:#{socket.assigns.schema.id}"

  if connected?(socket) do
    {:ok, _session} = Issho.Live.join(MyApp.Issho, socket.assigns.current_user, doc)
    Issho.Live.subscribe(doc, :self, events: ["canvas.*"])
  end

  {:ok, assign(socket, doc: doc, graph: socket.assigns.schema.graph)}
end

def handle_info({Issho.Live, "event", _room, %Issho.Event{}}, socket),
  do: {:noreply, assign(socket, :graph, Hakushi.Issho.graph(MyApp.Issho, socket.assigns.doc) || socket.assigns.graph)}

# The room's other news (its people, their cursors) is the canvas's.
def handle_info({Issho.Live, _event, _room, _payload}, socket), do: {:noreply, socket}
```

The threads are kept in your Issho instance's store (`:store`), whatever
the canvas's document is. See Issho's comments guide for who may
comment, mentions, and agents that answer.

## The app's own changes

In the LiveView of the canvas, `Hakushi.Live.change/2`. From anywhere (a
job, another LiveView), `Hakushi.Issho.change(MyApp.Issho, document,
change, author: current_user)`, while someone has the canvas open; it
answers `{:error, :not_running}` when no one has, and then you change
your own tables. `:author` is a key (`"server"`, the default) or your
actor: `on_change` gets it as `by`, as everyone's. `Hakushi.Issho.graph/2` reads the graph a room has.

## Agents

An agent works on a shared canvas as a person does, and the people see
it: its avatar in the corner, its pointer with its name, the node it
drags going with its pointer, its changes landing. `Hakushi.Issho.tools/0`
gives it the canvas's verbs, however it reaches your app:

| Tool | What it does |
|---|---|
| `read_canvas` | the nodes (place, size, data, group) and edges |
| `point_at` | moves its pointer to a node or a point, and selects nodes |
| `add_node` | a node of a type the canvas draws, at a place |
| `move_node` | drags a node to a place: the people see it go |
| `connect_nodes` | an edge, from a handle to a handle |
| `update_node` | a node's data, type or lock |
| `delete_items` | nodes, with their edges, and edges |
| `draw_on_canvas` | strokes of ink, or a ring around a node: the people see the pen draw |
| `draw_shape` | a shape (rectangle, ellipse, diamond, triangle, text) or a line of its own, with its text and style; or a shape's text, style and rotation |
| `add_image` | an image from an address of a host the app allows, or a file of the app's by name, with its alt text and caption: the people see its placeholder while it comes |

A person may pause or stop an agent at work (Issho's agents bar,
`Issho.Work`): then it reads the canvas and does nothing else on it, not
even go there, and the tool tells it why, as Issho's own tools do.

Its pointer goes to where it works and drags as a hand does, in a straight
line: only where it goes is sent, and each browser draws the way there
(the pointer, and the node it drags), so the people follow it. Others'
pointers glide the same way, which smooths a person's too. For changes
that land at once:

```elixir
config :hakushi, Hakushi.Issho, travel: false
```

It moves with Issho's motion (`Issho.Motion`): only when someone of the
room sees the canvas, within a tool call's time budget.

### An agent that draws

On a canvas with ink (`Hakushi.Kits.Ink`), `draw_on_canvas` draws strokes
through points of the canvas, or a ring `around` a node, to circle it.
The people see its pen go to each stroke and draw it, the line growing
under its pointer as a person's does, and each stroke lands as a node
when the pen lifts: they move it, erase it and undo it as any.

Whether the people watch it draw is the app's to say, and the agent's
for one drawing:

```elixir
config :hakushi, Hakushi.Issho,
  ink: [
    live: true,    # false: strokes land at once, unseen as drawn
    speed: 900,    # how fast the pen goes, in pixels of the canvas a second
    max: 8_000     # the most one drawing takes, in milliseconds; then the rest at once
  ]
```

The agent's `live: false` lands a drawing at once. It also lands at once
with `travel: false` (Hakushi's or Issho's), and when no one in the room
shows the canvas, or each of them prefers reduced motion: an agent draws
for someone who watches.

### What a person sees

An agent reads the graph, not the screen, and some of what a person sees
is not in the graph: how big a node is drawn, and what is on top of what.
Each browser with the canvas open says what its person sees (once a
second at most, when it changes, in their awareness), and `read_canvas`
gives it, of the person it reads about (`of`), else of the one it acts
for, else of anyone with the canvas open (`seen_by`):

* a node on their screen has its `box` there, `[x, y, width, height]` in
  the canvas's coordinates;
* an edge that passes under other nodes on its way has `passes_under`,
  their ids: to the person, the line seems to end at such a node, which
  it does not join.

So an agent asked "why is this note connected to that one?" can say that
it is not: the edge goes from the note to another card, under it. The
others' browsers get none of it.

### Following, and the page

A click on someone's avatar, on the canvas or in the page's people list
(`Issho.Components.people/1`), follows them: the canvas shows what theirs
shows, or keeps an agent's pointer on the screen. A wheel, a press or a
key on the canvas stops following. On a page with Issho's pointers
(`Issho.Components.pointers/1`), they draw everyone's pointer on the
canvas too, at the same point of the canvas for each person, and one
pointer goes from the page onto the canvas and back.

### A canvas in a page of another room

A canvas shared in its own room, in a page of another room (a canvas
embedded in a Kotoba document): its people are its room's, wherever they
are. Someone reading the document sees the cursor of someone on the
canvas's own page, and the other way round, and the same canvas's
changes, avatars and following. The page's pointers, of the document's
room, draw no one on the canvas (see Issho's guide): the canvas draws its
people, except those whom Issho's pointers of its own room draw (on the
canvas's own page, when it has them).

* Following someone into the canvas, from the page's people list, follows
  them on the canvas too; following someone from the canvas's avatars
  brings the canvas into view, once, when it is off the screen.
* A canvas in a page twice (the same canvas, two embeds) is one person in
  the room: the copy they use (their pointer, a press, the focus) says
  where they are, and the other copies say nothing.
* A canvas that goes while its LiveView stays (an embed deleted from the
  document) lets its room go: the LiveView leaves the room with its last
  canvas there, and the others see the person go.

See [In a page](embedding.md).

A canvas that shows its people (its avatars) says whom it follows:
"Following Ada", with Stop, under the avatars, in their colour, and a ring
of it around the canvas. The look is Issho's, the same as its people
list's and every other block's on the page: the line carries Issho's
classes (`issho-following`, `issho-following-stop`) besides its own
(`.hakushi-following`, the ring `.hakushi-following-ring`), the canvas
carries `data-ui-following` while it follows, and Issho's properties, on
the page, restyle all of them at once:

```css
:root {
  --issho-following-background: #111827; /* the person's colour */
  --issho-following-text: #fff;
  --issho-following-ring: none;          /* a ring of the person's colour */
}
```

`--hakushi-follow-color` is the followed person's colour, for your own
rules; the words are the `following` and `stopFollowing` messages.

A canvas that should take no part in following, though the other blocks
of its room do, says so with `issho`'s `follow: false`: its avatars are not
buttons, it shows no one followed, and its view stays its person's.

Its changes go the way of everyone's: the canvas's rules check them, and
your `on_change` is asked once for each. A canvas no one has open is the
document your policy defines for it (`Hakushi.Issho.document/2`, see
[The room's document](#the-room-s-document)): the agent opens it from
there, with no browser at all, and people who open the canvas later see
its changes. Without it, the agent works only on a canvas someone has
open.

Give them to your app's own model, through its API, with Issho's room
tools:

```elixir
tools = Issho.Tools.room() ++ Hakushi.Issho.tools()
ctx = Issho.Tool.Context.new(MyApp.Issho, assistant, on_behalf_of: current_user)

Issho.Tools.schemas(tools)                    # the tools of a request to the model
Issho.Tools.call(tools, name, input, ctx)     # each tool use in its answer
```

or serve the same tools to MCP clients (with `{:sake, "~> 0.4"}`):

```elixir
defmodule MyApp.MCP do
  use Issho.MCP,
    name: "my-app",
    version: "1.0.0",
    tools: Issho.Tools.room() ++ Hakushi.Issho.tools()
end
```

See Issho's agents guide for who an agent is, and for whom it acts.

The repository's development app serves them at `/mcp`. With `mix dev`
running and the schema open at http://localhost:4099, connect Claude Code
with `claude mcp add --transport http hakushi-dev http://localhost:4099/mcp`,
ask it to lay out the tables of `atelier:schema`, and watch.
