Group direct messages (GRYT-679) - #87
Merged
Merged
Conversation
More than two people in a conversation. Most of the shape was already there from GRYT-671 — `kind` on the conversation, a membership table rather than two columns, a list of other members in the view — so this is largely finishing what was started. Adding somebody to a one-to-one does not convert it. `dm:group:create` makes a new conversation and the pair conversation stays exactly as it was, with its history. That is a privacy decision before it is a technical one: what two people said to each other should not become readable by a third because somebody tapped "add". Discord draws the same line. There is a test that holds it, including that the third person cannot fetch the pair history. A group's id is random where a one-to-one's is derived from its pair. The derived id is what makes opening a DM idempotent from either end, and it cannot survive membership changing — add somebody and the id would change, so the conversation would read as a different one with no history. Anybody in a group may add somebody; anybody may leave; nobody may remove anybody else. That last one keeps a moderation model out of a conversation that has no moderators, and it is the answer that needs no further decisions later. Leaving is not hiding, and the two are kept apart deliberately. Hiding is your own sidebar and a message brings it back; leaving drops the membership row, so nothing arrives afterwards and the history stops being yours. The last person out purges the conversation, which is the same sweep `server:leave` already runs, at the other moment a conversation can empty. A group has no name until somebody gives it one. The fallback is built from who is in it, by the clients rather than here, so it follows a rename instead of going stale. The view keeps `other` alongside the new `members`, pointing at the first of them. It is redundant on purpose: a client built before groups reads it, and dropping it would turn each of those into a crash rather than a client that simply does not know what a group is. Ten people per group. A cap because every message fans out to all of them and the member list rides in every `dm:list`; the number is Discord's and what matters is that it is decided here rather than by whoever first tries a hundred. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
`dm:group:rename` becomes `dm:group:update`, taking both. One event because the screen that changes either changes both, and a client sending them separately would draw a group renamed but not re-pictured for a round trip. `icon_file_id` holds an upload and nothing else. A group with none is drawn from its name by the clients, the same way a server with no icon is — storing a generated image would freeze it against a group that later gets renamed, and the drawing is cheap enough that there is no reason to keep it. So `null` on either field means "go back to the drawn one" rather than "has none": no name reads off the members, and no upload draws from that name. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
`dm:group:create` accepts `iconFileId`. The client uploads the picture before the group exists, so without this it would have to wait for `dm:opened` to learn the id and send a second event — and the group would exist, briefly, wearing the drawn icon it was not meant to have. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
More than two people in a conversation. Server half.
Most of the shape was already there from #82 —
kindon the conversation, a membership table rather than two columns, a list of other members in the view — so this is largely finishing what was started.The decision worth your time
Adding somebody to a one-to-one does not convert it.
dm:group:createmakes a new conversation and the pair conversation stays exactly as it was, history intact.That's a privacy decision before it's a technical one: what two people said to each other shouldn't become readable by a third because somebody tapped "add". Discord draws the same line. There's a test that holds it — and it also asserts the third person can't
chat:fetchthe pair history.If you'd rather it converted, say so — it's a small change here and a large one for anybody who's already used a DM.
The rest
server:leavealready runs, at the other moment a conversation can empty.otheris kept alongside the newmembers, pointing at the first of them. Redundant on purpose: a client built before groups reads it, and dropping it turns each of those into a crash onother.nicknamerather than a client that just doesn't know what a group is.dm:list. The number is Discord's; what matters is that it's decided here rather than by whoever first tries a hundred.Testing
Nine new cases: three people all see it, the pair conversation survives untouched and stays unreadable to the third, delivery reaches members and nobody else, anyone in it can add, leaving stops delivery and doesn't come back, the two-person floor, an outsider refused, naming reaches everybody, and leaving a one-to-one refused.
347 pass (was 338). Typecheck clean, no new lint warnings.
Not in this PR
Client and mobile UI. Calling is GRYT-680 and lands on top of this.
🤖 Generated with Claude Code