CASE STUDY 03 · SDK
Pulse
Drop-in web components that add collaboration to any website.

Comment threads, live cursors, presence indicators, reactions, and P2P shared data — added to any site via simple HTML tags. Framework-agnostic, self-hosted, no vendor lock-in.
5
primitives (comments, cursors, presence, reactions, P2P data)
0
framework dependencies for host apps
2
state planes: Redis ephemeral, Postgres durable
1
HTML tag to integrate
THE PROBLEM
Adding collaboration to a product — comments, presence, live cursors — usually means either a hosted service you pay per-seat forever (and pipe your users' data through), or weeks of WebSocket plumbing you build badly once and maintain forever.
Existing SDKs are React-first. If your product is Vue, vanilla JS, or server-rendered HTML, you're out of scope.
WHAT I BUILT
A collaboration SDK delivered as native web components: drop `<pulse-comments>`, `<pulse-cursors>`, or `<pulse-presence>` into any page — React, Vue, plain HTML, doesn't matter — point it at your self-hosted Pulse server, and it works.
The server handles rooms, identity hand-off, message fan-out, and persistence. Comment threads anchor to page elements; cursors and presence are ephemeral state broadcast over WebSocket. P2P shared data adds synchronized application state on the same rails — it auto-saves server-side, so late joiners get the latest state instantly.
ARCHITECTURE
any website (React / Vue / plain HTML)
└─ <pulse-comments> <pulse-cursors> <pulse-presence>
│ WebSocket + REST
▼
Fastify 5 server
├─ room fan-out (WS)
├─ Redis ── ephemeral state (cursors, presence)
└─ PostgreSQL ── durable state (threads, reactions)- Web components mean the host page's framework is irrelevant — Shadow DOM isolates styles both ways, so the SDK can't break the host and the host can't break the SDK.
- Ephemeral and durable state take different paths: cursor positions fan out through Redis pub/sub and are never written to disk; comments go through Postgres with optimistic UI on top.
- Self-hosting is the point: one Docker container next to your app, your users' data stays yours.
STACK — AND WHY
Lit 3
The lightest way to ship real web components with reactive state — the whole client is a few KB, not a framework.
Fastify 5
HTTP + WebSocket on one server with schema validation.
Redis
Pub/sub fan-out for high-frequency ephemeral state (cursors move a lot; none of it belongs in Postgres).
PostgreSQL
Threads, reactions, and anchors need durability and querying.
THE HARD PARTS
Anchoring comments to a page you don't control
A comment pinned to an element must survive the host site's re-renders, class-name churn, and layout shifts. Anchors are computed against DOM structure with graceful degradation — when an anchor can't be re-resolved, the thread demotes to page-level instead of disappearing.
Cursor traffic without melting the server
Live cursors are the highest-frequency, lowest-value data in the system. Client-side throttling, server-side coalescing, and Redis pub/sub keep fan-out linear; nothing about a cursor ever touches the database.
An SDK's API surface is forever
Apps embed the tags and never update. Attribute names, events, and wire protocol had to be versioned from day one — the server negotiates protocol versions so old embedded clients keep working.
WHAT IT TAUGHT ME
- Web components are the right substrate for embeddables — Shadow DOM isolation solves the 'your CSS broke my widget' class of bugs structurally.
- Separating ephemeral from durable state early made scaling decisions obvious instead of painful.
- The hardest part of an SDK isn't the code — it's designing an API you can never break.