feat(service): a workspace's chat is sealed for following over Neon Realtime
Part of making workspace chat live over Neon Realtime. On main (#3906 has merged). One change: a workspace's chat is sealed for following.
What changes
- New live read:
live.workspaceChat(GET /api/live/workspace-chat?workspaceId=&userId=) inlive-procedures.ts. It answers the window's chat read as five sealed statements:
| Part | What it holds |
|---|---|
self | the reader's own member and how far it has read |
members | the roster, each member named as the held read names it (a person by their account's name, an agent by its title or model) |
channels | the channels, with their purpose |
messages | each channel's newest 200 messages that visibleTo lets the reader see (LIVE_CHANNEL_MESSAGES, a row_number() per channel), plus every ask still open however far back it is, so For you keeps listing it |
reads | the reader's per-channel read cursors |
- Who can read it. Every part returns nothing once the account leaves the workspace or turns Chat off.
- Like the other live reads from #4228, it checks the session against
?userId=. Another account's session gets 401, which the client reads as signed out.
- Like the other live reads from #4228, it checks the session against
- Polling. Like main's other live reads, it answers polled when the reader asks to poll or Realtime can't seal it.
- Sealing writes nothing. Someone who hasn't joined (#3906) is not found.
- Building the answer.
workspaceChatOfLiveRowsinlive-wire.tssorts the rows the way the held read does and builds the held read'sChatAnswer, with no cursor.
The window can't scroll back, so a channel past 200 messages shows its newest 200.
Tests
-
PGlite parity (
workspace-chat.test.ts). The test chat has:- a reply in a thread
- a DM
- a topic with a purpose
- an answered ask
- a per-channel read
- more messages in one channel than the bound
For a member, the live rows build exactly what the held read returns from the start, kept to each channel's newest 200. For an outsider, and for someone with Chat off, every part is empty.
-
Immutability.
live-statements.test.tsholds the five parts tomutableFunctionsOf(none found) and to Realtime's Drizzle adapter. -
Procedure (
live-procedures.test.ts):- sealed for the bearer
- not found for someone who hasn't joined, a stranger, or someone with Chat off
- signed out for another account's session
-
New JSON Schema goldens for
workspaceChatLiveAnswerSchemaandworkspaceChatLiveRowsSchema. -
Checks run one at a time on the stack's top: tsc per touched package, biome, oxlint, knip, discarded-effect, repository checks, and the affected vitest suites.