A conversation outlives the window showing it

Performancekamo-internal
Shipped
August 28, 2026 at 2:12 AM UTC
Author
Kamo
Commit
1296159

Opening a chat window cost seven requests before a message rendered — history, members, receipts, mark-read, the unread snapshot, the counterpart's avatar and the composer policy — plus up to one translate POST per message, and on the server a PHI audit row written per message, individually. Closing the window threw all of it away, so glancing at a conversation and back paid for a hundred rows to rebuild the list that had just been discarded. Minimizing was never the expensive case: the dock keeps a minimized window mounted. Closing was, and so was the maximized navigator, which opens and demotes windows all day. The window's lifetime and the conversation's are not the same thing. A window now ACQUIRES a conversation and RELEASES it, and the conversation outlives the release by five minutes (@kamo/tool-core's keep-alive registry, bounded by time AND count, never evicting one a window is still showing). Reopening inside that costs nothing, and because the socket was never dropped what comes back is a current conversation rather than a cached one. Past the keep-alive, chat-core's resume() asks only for what arrived after the newest message it holds. The same registry now carries the four windows that each hand-rolled the same bug: SMS, AI, support tickets and social all held their thread in a useState and changes is that the re-fetch happens over the conversation instead of over a spinner. On social that was more than a flash: blanking the list also blanked lastInboundTs, so the composer read Meta's 24-hour reply window as CLOSED on a conversation that was perfectly repliable. The render READS the conversation and the effect HOLDS it. Only an effect guarantees a matching release — a discarded render's hold would be a subscription nothing ever lets go of — but an effect runs after the first paint, which would leave a reopened window flashing empty for a frame before showing what it already had. Reading is not holding, so the peek is safe where an acquire is not. The acquire waits for a known viewer. getMemberIdString() answers '' until useUserInfo loads, and myMemberId decides whether each message is the viewer's own; the old code built the conversation anyway and REBUILT it when the real id arrived, which a registry keyed on the session cannot do. Not building one under an identity we do not have yet is the honest version, and it drops a wasted full load. Also off the per-open path: the unread re-read now fires only when a conversation genuinely starts, not when a window adopts a live one whose unread is already zero and held there; member avatars are memoized as promises, so N windows share one directory read and a reopen costs none; and the composer lock is seeded from the last answer, so a window over a frozen conversation stops rendering an open composer that snaps shut a round trip later. What survives a hard reload is metadata and nothing else. conversationSnapshot holds participant NAMES — which toolWindowSnapshot already persists inside the window title — and two booleans of composer state, so a restored window is not a titled conversation with nobody in it. No message bodies, ever: MediaService audits chat reads as PHI, and a transcript in storage is a disclosure with no read behind it. Bodies are always re-fetched. Sign-out drops the live conversations too, which clearing storage cannot reach. The BFF stops parsing a hundred-message page into objects and serializing them straight back for no change to the bytes, and carries an ETag so a repeat read of an unchanged page costs a validator instead of a transcript. Upstream is still called every time — the OTK is single-use and MediaService is the only thing that can say whether this member may read this session, so a 304 served without asking would be a cache answering authorization questions.

All changes

Like what you see shipping?

Every one of these updates lands in your workspace automatically. Start free and watch it grow week after week.

Start Free ForeverView Pricing