Skip to content
Apps— browse docs
On this page

Step 6: Who's around

useCollabState hands back peers for free — and then you run the whole thing for real.

useCollabState already gave you data and actions. It also gives you peers — everyone with this app instance open right now, the same shape useDocumentPresence gives a tool:

const { data, actions, peers } = useCollabState({ … })
const others = peers.filter((p) => !p.isSelf)

Add a facepile next to the headline. Edit the code below:

Who's around
src/app.tsx Lines 23, 28 and 49 to 61 highlighted.

Edit and the example re-runs. Tab indents; press Escape to leave the editor.

Running · your edits, live
Starting the example…

You should see: nothing new in this one pane — there's only one of you here. The demo below proves it works, with a second identity for real.

Two people, one lobby

Same shape useCollabState gives your app, mounted twice — two independent replicas, merged live, exactly as if two members had your app open on two different computers. Edit the headline in either pane; star "Aria of the North" in one and watch it appear in the other:

Two members, one lobby
Edit the headline in either side, then star Aria of the North.
Starting the example…

Move your pointer inside a pane. The other one draws it (the preview surface around each pane draws the cursors, not the code above — the right division of labour; see the Presence guide). That's the same convergence useCollabState gives your real app — this demo just opens the room directly so two browser tabs on one page can show it to you.

Tip:

You didn't write any of that merge logic. field.value resolves concurrent writes last-write-wins, field.list merges concurrent inserts — the same guarantees every vvd document makes, now backing your app's own shared state.

Beyond this lobby

This app never created a document of its own — it only read the world and kept one shared page of state. That's plenty for a front door. When your app needs more than one page — a story with real chapters, a campaign with real sessions, each separately shareable — an app instance is itself a project, and it can own sub-documents of its own. That's Owning a space, and it's also most of what the next build-along is about.

Run it for real

vvd create Lobby --app
Expected output:
→ Creating app Lobby in /Users/you/lobby — from the Hello World template

✦  Lobby — a brand-new app, ready to come alive.

Next:
cd lobby
vvd run    # render it live in your world — hot-reloads as you edit
vvd save   # save a new version (a private draft)

✓ Created Lobby (app) → /Users/you/lobby

vvd create --app writes the same app you just built — headline, pinboard, directory, presence — with a little extra polish (a Pinned section with an empty state, and the world's name and doc count) as src/app.tsx. cd lobby && vvd run puts it in a real world in a couple of seconds, hot-reloading as you type, with a real second person able to open the same URL and watch your headline change.

Recap

  • A tool consumes the host; an app provides one. Same contract, opposite roles.
  • defineApp needs three things — Host, Surface, tools — and validates them the moment your module loads, not when it renders.
  • AppHost composes Host → Surface → ToolHost, so a tool you host never learns it's inside your app.
  • useCollabState gives your app one shared room per instance — data, actions, and peers — with zero wiring.

Next steps