tino.collab

Real-time collaboration via Yjs CRDT over WebSockets.

Each open file gets a YRoom backed by a pycrdt Doc. Multiple clients connect to the same room and edits are merged via the Yjs sync protocol. When the last client disconnects, the document is flushed to disk.

class tino.collab.CollabManager(data_dir: Path, file_service: FileService, *, auto_save: bool = True, room_ttl: int = 300)

Manages Yjs collaboration rooms for concurrent file editing.

Rooms are created lazily when the first client connects to a file and torn down (with a disk flush) when the last client disconnects.

async edit_file(slug: str, file_path: str, transform) → str

Atomically transform a file’s current content and save it to disk.

transform(text) returns (new_text, splices) (see apply_edits()). When the file is open in a room, the transform runs against the live document and the splices are applied to its YText as incremental edits in one transaction, so they merge with collaborators’ (unsaved) changes instead of replacing them. Otherwise the file is read from disk. Holding the per-path lock keeps a room from being created or flushed in between.

Returns the new ISO mtime. Raises FileNotFoundError if the file does not exist and ValueError if it is binary; errors raised by transform propagate before anything is changed.

async evict_under(slug: str, prefix: str) → None

Evict every room whose path is under prefix (used for dir operations).

get_content(slug: str, file_path: str) → str | None

Return the live YText content if a room exists, else None.

lock_path(slug: str, file_path: str)

Hold the per-path lock and evict any existing room without flushing.

Used by file delete/rename handlers to atomically tear down collab state and prevent the race where a pending flush re-creates the file the client just asked the server to remove.

async reload_rooms(slug: str, paths: list[str]) → None

Reload room content from disk after external file changes.

Called after git restore or other operations that change files on disk outside the CRDT. Connected clients receive the update via sync.

async serve(websocket: WebSocket, slug: str, path: str) → None

Handle a WebSocket connection for collaborative editing of a file.

async shutdown() → None

Cancel pending TTL tasks and flush all active rooms to disk.

start() → None

Called on application startup (reserved for future use).

class tino.collab.FastAPIChannel(websocket: WebSocket, path: str)

Adapts a FastAPI WebSocket to the pycrdt Channel interface.

property path: str

The channel path.

async recv() → bytes

Receive a binary message from the client.

async send(message: bytes)

Send a binary message to the client, serialized with a lock.