# `Issho.Comments`
[🔗](https://github.com/hattoriai/issho/blob/v0.4.0/lib/issho/comments.ex#L1)

Threads of comments on the places of a room's components: a range of a
text, a node of a canvas, a cell of a grid, or the page as a whole.
People and agents start them, reply, edit and delete their own, react,
mention each other, resolve and reopen them.

A room's threads are a document of the room, `"board:42/issho.comments"`
(`document/1`), with Issho's own engine (`Issho.Engine.Comments`): it is
Issho's, whatever your policy's `document/2` says of that id, kept in
the instance's store, shared and live as any document is. Your policy
says who may comment: a change to it asks for `:comment`, not `:write`,
so a reader with a `can: :read` grant may comment when your policy
allows it (`Issho.Policy`).

## From your server

    ada = %{id: "user:ada", name: "Ada"}
    place = %{"doc" => "board:42", "at" => %{"node" => "n7"}}

    {:ok, %{thread: thread}} =
      Issho.Comments.start(MyApp.Issho, "board:42", ada, place, "Should this go first?",
        mentions: ["agent:claude"])

    {:ok, _} = Issho.Comments.reply(MyApp.Issho, "board:42", claude, thread, "Yes: it blocks the rest.")
    {:ok, _} = Issho.Comments.resolve(MyApp.Issho, "board:42", ada, thread)
    {:ok, threads} = Issho.Comments.threads(MyApp.Issho, "board:42")

An actor is an `Issho.Actor`, or an `Issho.Identity` (an agent and whom it
acts for). From a LiveView, an agent's participant or a browser, the
same changes are operations of the comments document (`Issho.Comments.Ops`),
committed under the policy as any change.

## Who wrote it, and when

Each comment names its author and its time. Before a change is
accepted, Issho holds them to the writer: the author is the signed
identity of whoever made the change (its key, name, kind, avatar, colour
and whom it acts for), and the time is within a few minutes of the
server's clock, so a browser cannot write as someone else, or in the
past. A thread's place is in the room.

## Events

After each change is saved, the room has its events (`Issho.Events`):

  * `"thread.created"`: `%{thread: id, comment: id, place: place, body: text, mentions: [key]}`;
  * `"comment.created"`: every new comment, the first of a thread too,
    `%{thread: id, comment: id, place: place, body: text, mentions: [key], reply: boolean}`;
  * `"comment.edited"` (`body`, `mentions`) and `"comment.deleted"`;
  * `"thread.resolved"` and `"thread.reopened"`: `%{thread: id, place: place}`.

An agent mentioned in a comment hears it (`wait_for_events` with
`mentions_me`), and answers in the thread (the `comment` tool).

# `delete`

```elixir
@spec delete(
  atom(),
  String.t(),
  Issho.Actor.t() | Issho.Identity.t(),
  String.t(),
  String.t()
) ::
  {:ok, map()} | {:error, term()}
```

Deletes one's own comment; a thread with none left goes.

# `document`

```elixir
@spec document(String.t()) :: String.t()
```

The comments document of a room.

    iex> Issho.Comments.document("board:42")
    "board:42/issho.comments"

# `document?`

```elixir
@spec document?(String.t()) :: boolean()
```

Whether a document is a room's comments.

    iex> Issho.Comments.document?("board:42/issho.comments")
    true
    iex> Issho.Comments.document?("board:42/n7/issho.comments")
    false

# `edit`

```elixir
@spec edit(
  atom(),
  String.t(),
  Issho.Actor.t() | Issho.Identity.t(),
  String.t(),
  String.t(),
  String.t(),
  keyword()
) :: {:ok, map()} | {:error, term()}
```

Edits one's own comment. Options: `:mentions`, whom the comment
mentions after the edit (none unless given).

# `react`

```elixir
@spec react(
  atom(),
  String.t(),
  Issho.Actor.t() | Issho.Identity.t(),
  String.t(),
  String.t(),
  String.t()
) :: {:ok, map()} | {:error, term()}
```

Adds the actor's reaction (an emoji) to a comment.

# `reopen`

```elixir
@spec reopen(atom(), String.t(), Issho.Actor.t() | Issho.Identity.t(), String.t()) ::
  {:ok, map()} | {:error, term()}
```

Reopens a resolved thread.

# `reply`

```elixir
@spec reply(
  atom(),
  String.t(),
  Issho.Actor.t() | Issho.Identity.t(),
  String.t(),
  String.t(),
  keyword()
) :: {:ok, %{thread: String.t(), comment: String.t()}} | {:error, term()}
```

Replies in a thread. Options: `:mentions`. Gives `{:ok, %{thread: id, comment: id}}`.

# `resolve`

```elixir
@spec resolve(atom(), String.t(), Issho.Actor.t() | Issho.Identity.t(), String.t()) ::
  {:ok, map()} | {:error, term()}
```

Resolves a thread.

# `start`

```elixir
@spec start(
  atom(),
  String.t(),
  Issho.Actor.t() | Issho.Identity.t(),
  map() | nil,
  String.t(),
  keyword()
) :: {:ok, %{thread: String.t(), comment: String.t()}} | {:error, term()}
```

Starts a thread at a place (`nil` for the page as a whole), with its
first comment. Options: `:mentions`, the keys of the actors it mentions.
Gives `{:ok, %{thread: id, comment: id}}`.

# `threads`

```elixir
@spec threads(atom(), String.t()) :: {:ok, [map()]} | {:error, term()}
```

The threads of a room, in the order they began.

# `unreact`

```elixir
@spec unreact(
  atom(),
  String.t(),
  Issho.Actor.t() | Issho.Identity.t(),
  String.t(),
  String.t(),
  String.t()
) :: {:ok, map()} | {:error, term()}
```

Takes the actor's reaction back.

---

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