Realtime (ephemeral multiplayer)

Presence, broadcast, and collaborative text on trellis/realtime separate from the persisted VCS op log and trellis/sync.

Trellis has two collaboration layers. Confusing them makes integration harder than it needs to be.

LayerPackageWhat it carriesWritten to .trellis ops?
Persisted graphtrellis/sync, VCSFile changes, entities, causal historyYes
Typed live readstrellis/schema, trellis/clientHydrated entity rows + relation resolve over /realtime WSYes (reads persisted state)
Ephemeral meshtrellis/realtimePresence, chat, cursors, live UI stateNo

Use sync when you need auditability, merge, and replay. Use realtime when you need fast, disposable signals (who is online, typing, cursor position) that should not bloat the op log.

Use the typed SDK when you need live UI on graph entities (nav trees, task boards, CMS-like collections). See Typed SDK, Schema API, and Server API.

Try it (simulated room)

Native Vue on the docs site (same bundle as the iframe demos):

Active user avatars

Presence on trellis/realtime. Simulated peers in one MemoryHub.

Loading realtime…

Full primitive tabs (chat, cursors, text) via embed no WebSocket server required:

Loading realtime demo…

Simulated room. Peers run in this page. No server required. Full demo + live relay

Use the tabs inside the frame to switch primitives:

Loading realtime demo…

Simulated room. Peers run in this page. No server required. Full demo + live relay

Loading realtime demo…

Simulated room. Peers run in this page. No server required. Full demo + live relay

Loading realtime demo…

Simulated room. Peers run in this page. No server required. Full demo + live relay

API sketch

import {
  BroadcastChannelTransport,
  MemoryHub,
  RealtimeRoom,
  RealtimeText,
  WebSocketRelayTransport,
} from "trellis/realtime";

// Same-tab / simulated peers (docs embed uses this)
const hub = new MemoryHub();
const room = RealtimeRoom.join({
  transport: hub.connect("peer-a"),
  initialPresence: { name: "Ada", color: "#6d5bfa" },
});

room.onPresence((peers) => console.log(peers));
room.broadcast("chat", "message", { text: "hello" });

// Cross-tab or cross-browser (needs a relay)
const relay = RealtimeRoom.join({
  transport: new WebSocketRelayTransport({
    id: crypto.randomUUID(),
    url: "ws://localhost:8231/rt",
  }),
  initialPresence: { name: "Ada", color: "#6d5bfa" },
});

const doc = new RealtimeText({ peerId: relay.selfId, room: relay });

Local full demo + live relay

From the trellis repo:

just realtime-demo    # builds bundle, serves demo/realtime, opens browser
just docs-realtime-sync   # copy bundle + embed into trellis.computer www

Live relay mode (?live=1) syncs real browser windows through the demo server's WebSocket at /rt. Simulated mode (default, including the embeds above) keeps every peer in-process.

When to persist instead

If chat or edits must survive refresh, belong in code review, or merge with branches, route them through VCS ops or sync, not trellis/realtime. Studio whiteboards combine a persisted file with live scene updates see Projections and whiteboards.