C.W.K.
Stream
Lesson 02 of 04 · published

REST + SSE, Not a WebSocket

~11 min · rest-sse, websocket, transport, producer-leg

Level 0Cold Draft
0 XP0/33 lessons0/12 achievements
0/100 XP to next level100 XP to go0% complete
"Choose the transport by the shape of the traffic. Rekindle has nothing the backend must continuously read — so plain HTTP wins."

Match the Transport to the Traffic

A WebSocket is a persistent, bidirectional pipe — powerful, and more complex to run and reason about than plain HTTP. The right question isn't "which is fancier" but "what shape is the traffic?" Rekindle's traffic is simple: you send a message and get a streamed reply; you select text and fire a one-shot rewrite. Nothing flows continuously from the editor into the backend. So Rekindle uses REST to bind and SSE to stream — the same embed model cwkBonfire uses — and skips the WebSocket entirely.

Why Cinder Needs a WebSocket and Rekindle Doesn't

The contrast with Cinder is the whole lesson. Cinder rides beside Photoshop, whose UXP plugin continuously pushes state the backend must read — a real producer leg, a constant upstream flow that justifies a persistent socket. Rekindle has no producer leg. The document context is pushed per turn, not streamed continuously; there's no live feed the backend must keep reading. Same family, different traffic shape, correctly different transport.

Surface   Upstream flow                 Transport
-------   ---------------------------   ----------------------
Cinder    Photoshop pushes constantly   WebSocket (producer leg)
Rekindle  a message, then a reply       REST (bind) + SSE (stream)

# No continuous producer -> no WebSocket. Plain HTTP is the right size.

REST to Bind, SSE to Stream

Concretely: a REST call binds the document to a conversation and sends a turn; the reply streams back token-by-token over SSE, exactly as cwkPippa's own chat does. CMD+K is even simpler — a single POST that returns a rewrite. Both fit plain HTTP perfectly. Picking the smaller sufficient transport isn't timidity; it's the same discipline as borrowing the smallest sufficient layer (Track 3) and reusing the existing engine (Track 5). Don't run a persistent socket for traffic that a request-and-stream already serves.

Code

REST to bind and send; SSE to stream the reply·typescript
// Bind + send a turn over plain HTTP (no persistent socket).
const res = await fetch(`/api/chat`, {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({ conversationId, message, hostContext }),
});

// The reply streams back token-by-token as Server-Sent Events —
// the same streaming cwkPippa's own chat uses.
for await (const event of readSSE(res.body)) {
  appendToken(event.data);
}

// CMD+K is even simpler: a single POST that returns one rewrite.
// Nothing here needs a WebSocket, because nothing streams UPSTREAM.

External links

Exercise

For three features — a live cursor-sharing collaboration view, a chat with streamed replies, and a one-shot 'summarize this' button — decide the transport each needs. Identify which one has a producer leg (continuous upstream) and therefore justifies a WebSocket, and which two are fine on REST + SSE. Notice how the producer-leg test decides all three.
Hint
Live cursor-sharing streams positions upstream continuously → WebSocket. Streamed chat is request-then-stream-down → SSE. One-shot summarize is a single request/response → plain REST. Only the first has a producer leg; the other two don't need a socket.

Progress

Progress is local-only — sign in to sync across devices.
Spotted a bug or have feedback on this page?Report an Issue

Comments 0

🔔 Reply notifications (sign in)
Sign inPlease sign in to comment.

No comments yet — be the first.