Issho

Issho β€” Collaboration for Phoenix

Issho (δΈ€η·’, "together") is collaboration for Phoenix: one engine for people and agents working together live, under every component on a page. Build apps like Notion, Figma or Linear, and bring any UI that follows the Issho contract: Kotoba's rich text, Masu's grids, Hakushi's canvas, or your own.

Status: early release. Rooms, documents, LiveView sessions, grants, the browser client, the people list and theme, the page's pointers, undo across a page, agents however they connect (MCP, your app's own model), and the bindings of Kotoba, Hakushi and Masu are here. Following across pages and the installer come next; before 1.0, a minor version may change the API.

Live collaboration in the Hattori libraries is Issho's: Kotoba's shared editing (from 0.6), Masu's grids' people (from 0.4) and Hakushi's canvases. Each library works on its own without it; with it, a page of them has one connection, one list of people, one policy, and agents that work across all of them.

Installation

def deps do
  [
    {:issho, "~> 0.2"}
  ]
end

Issho needs Elixir 1.18 or later, Phoenix 1.7.14 or later and, for Issho.Live, Phoenix LiveView 1.1 or later.

It gives your app:

A sketch

# In your supervision tree, after the PubSub:
{Issho,
 name: MyApp.Issho,
 pubsub: MyApp.PubSub,
 endpoint: MyAppWeb.Endpoint,
 policy: MyApp.Collaboration,
 store: {MyApp.CollabStore, MyApp.Repo}}
defmodule MyApp.Collaboration do
  @behaviour Issho.Policy

  @impl true
  def authorize(who, "board:" <> id, action),
    do: if(MyApp.Boards.allowed?(who.claims["user_id"], id, action), do: :ok, else: {:error, :forbidden})

  @impl true
  def document(_who, "board:" <> id),
    do: {:ok, %{engine: Issho.Engine.Map, initial: MyApp.Boards.content(id)}}
end
# A channel on a socket of your endpoint:
defmodule MyAppWeb.IsshoChannel do
  use Issho.Channel, instance: MyApp.Issho
end

# A LiveView signs the browser's join token:
token = Issho.Token.sign(MyApp.Issho, current_user, "board:42",
  can: :write, claims: %{"user_id" => current_user.id})
# An agent joins the same room:
{:ok, claude} = Issho.join_as(MyApp.Issho, agent, "board:42", on_behalf_of: current_user)
{:ok, _doc} = Issho.Participant.open(claude, "board:42")
{:ok, _receipt} = Issho.Participant.transact(claude, "board:42", ops)

In the browser

import { joinRoom } from "issho"

const room = await joinRoom("board:42", { token, self: "user:ada" })
const doc = await room.open("board:42")

doc.on((event) => event.type === "change" && render(event.state))
doc.change([{ op: "set", path: ["title"], value: "Q3 plan" }])

room.aware({ place: { doc: "board:42", at: { path: ["title"] } } })
room.awareness.on((peers) => drawCursors(peers))
room.presence.on(() => drawPeople(room.presence.others))

Roadmap

Development

mix deps.get
npm ci --prefix assets
mix test        # includes the end-to-end test of the client on a real endpoint
mix test.js     # the browser client's unit tests
mix precommit   # compile with warnings as errors, format, credo, test
MIX_ENV=dev mix issho.build   # the bundles, into priv/static

License

MIT. See LICENSE.