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 (0.1). The server core, LiveView sessions, agents from Elixir and the browser client's core are here, and Hakushi's shared canvases run on them. The cursor overlay, following, the bindings of Kotoba and Masu, MCP and the installer come next; before 1.0, a minor version may change the API.

Installation

def deps do
  [
    {:issho, "~> 0.1"}
  ]
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))

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.