Step 7: See each other
useDocumentPresence lists who's in the document right now — no channel to open, no state of your own.
The platform already drew a cursor for you in the last step — that came free, from the surface around the tool. Now make your own UI say who's here, with one hook.
Why does presence come off handle, the thing useDocument returned? Because presence is a
per-document question — "who's in this document", not "who's online" — so it rides the
same live connection the document already opened. That's the whole design: no channel of
your own to open or tear down, and nothing written into the document itself. Presence is
ephemeral on purpose — when someone closes the tab, they're simply gone, with no stale row
to clean up.
Add this component to src/tool.tsx, above Board. DocumentHandle is the SDK's name for
the thing useDocument returns as handle — the prop is typed because the scaffold's
tsconfig is strict, and an untyped { handle } destructuring is an implicit-any error:
function PresenceStrip({ handle }: { handle: DocumentHandle | null }) {
const others = useDocumentPresence(handle).filter((p) => !p.isSelf)
if (others.length === 0) return null
return (
<span style={{ display: "flex", alignItems: "center" }}>
{others.map((p) => (
<span
key={p.userId}
style={{
width: 22,
height: 22,
borderRadius: "50%",
marginLeft: -6,
display: "inline-flex",
alignItems: "center",
justifyContent: "center",
background: p.color,
color: "#fff",
fontSize: 11,
fontWeight: 700,
}}
>
{p.name ? p.name.slice(0, 1).toUpperCase() : ""}
</span>
))}
</span>
)
}Then wire it up — three small edits, all in src/tool.tsx (the codec half at the top of the
pane is untouched this step): add handle to what BestiaryView pulls out of useDocument,
thread it into Board as a prop, and render <PresenceStrip handle={handle} /> next to the
Add button. Don't forget useDocumentPresence — and type DocumentHandle — in the
@vvd/sdk import. (Splitting rule for that merged import block, as ever: defineStateCodec
and field belong to src/codec.ts; everything else — PresenceStrip and newId
included — is src/tool.tsx.)
Edit and the example re-runs. Tab indents; press Escape to leave the editor.
You should see: nothing changes yet — there's only one of you in this pane. The next demo proves it works, with a second identity for real.
Move your pointer inside a pane. The other one draws it. Add a note in one — the coloured chip
appears in both headers, because both panes are running your PresenceStrip, reading the
same useDocumentPresence(handle).
Recap
useDocumentPresence(handle)lists everyone in the document right now —userId,name,color,isSelf.- It's ephemeral: presence lives in the awareness channel, never in your document.
- Cursors are drawn by the surface around your tool; the facepile is yours to build.