Skip to content

Let somebody hide a direct message (GRYT-678) - #86

Merged
sivert-io merged 1 commit into
mainfrom
claude/GRYT-678-hide-dm
Aug 29, 2026
Merged

Let somebody hide a direct message (GRYT-678)#86
sivert-io merged 1 commit into
mainfrom
claude/GRYT-678-hide-dm

Conversation

@sivert-io

Copy link
Copy Markdown
Member

Server half. Take a conversation out of your own sidebar without deleting anything.

The design decision, and it's the one to check

hidden_at goes on conversation_members, not on conversations. Each participant already has their own row there, so hiding is one person's answer about their own sidebar — you hiding it is invisible to them, and they get no event. A column on conversations would have hidden it for both, which is a different feature entirely.

dm:setHidden writes only the caller's row, and the server_user_id it writes comes from the verified token, never the payload.

A message brings it back

For whoever had hidden it. Without that, "hide" is quietly a way to never hear from someone again, and the only sign would be an unread count on a row that isn't there. Blocking is the feature that does mean that; it's on the roadmap as its own item and needs its own decisions.

Re-opening from the member list also un-hides, so there's a way back that doesn't depend on the other person saying something.

The un-hide is emitted as dm:opened — the event both clients already use to put a conversation into the list. A second event meaning the same thing would be a second path to keep in step. It runs after the send and its failure is swallowed, same reasoning as auto-roles: a sidebar that hasn't caught up must never be why a message didn't arrive.

Worth a look

  • The membership check in dm:setHidden. It refuses a conversation that isn't yours. Today that's belt-and-braces — the UPDATE would match no row anyway — but it's the check that keeps it harmless if the table ever gains a different key.
  • clearConversationHidden clears for everyone in the conversation. That looks like it contradicts "per-person", and doesn't: the hide is per-person; a new message is relevant to every participant, so it un-hides for each of them who had hidden it. It returns the ids it changed so only those people get told.
  • Nothing is deleted at any point, and there's a test named for it.

Testing

Six new cases: hiding leaves the other person's list alone, history survives, it comes back on a new message and the client is told, it comes back on re-open, it can be put back by hand, and somebody else's conversation is refused.

338 pass (was 332). Typecheck clean, no new lint warnings.

Not in this PR

Client and mobile UI — right-click and long-press respectively. Both are on GRYT-678 and follow once this lands.

🤖 Generated with Claude Code

Take a conversation out of your own sidebar without deleting anything.

`hidden_at` goes on `conversation_members` rather than on `conversations`,
and that is the whole design: each participant already has their own row
there, so hiding is one person's answer about their own sidebar. A column on
the conversation would have hidden it for both of them, which is a different
feature and not one anybody asked for. `dm:setHidden` writes only the
caller's row, and the id it writes comes from the verified token rather than
the payload.

A message brings it back, for whoever had hidden it. Without that, hiding is
a way to never hear from somebody again and the only sign is an unread count
on a row that is not there. Blocking is the feature that means the other
thing; it is on the roadmap as its own item and needs its own decisions.
Re-opening from the member list also un-hides, so there is a way back that
does not depend on the other person saying something.

The un-hide is emitted as `dm:opened`, which is the event the clients
already use to put a conversation into the list — a second event that meant
the same thing would be a second path to keep in step. It runs after the
send and its failure is swallowed, for the same reason auto-roles are: a
sidebar that has not caught up must never be why a message did not arrive.

Nothing is deleted at any point. There is a test that says so.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@sivert-io
sivert-io merged commit 14e9424 into main Aug 29, 2026
2 checks passed
@sivert-io
sivert-io deleted the claude/GRYT-678-hide-dm branch August 29, 2026 11:08
sivert-io added a commit to Gryt-chat/client that referenced this pull request Aug 29, 2026
The client half of Gryt-chat/server#86. Right-click a conversation, choose
Hide, and it leaves your sidebar. Yours alone — the other person's list does
not change and they are not told — and nothing is deleted.

The DM rows had no context menu at all, so this adds one. Same shape as the
channel rows', with the other person's name as the group label.

Hiding the conversation you are reading puts the selection back on the
channels. Staying in it would leave the view pointing at something that is no
longer in the list, with no way back to itself; the conversation is still
readable, by opening it again from the member list.

The row goes when the server says so rather than on the click. Dropping it
locally would look right here and leave it on the phone until something else
refreshed the list — the reason `hidden_at` is on the server is that the two
agree.

The item says what it does not do. "Hide" on its own reads as a soft delete
to enough people that the second line is worth the room. Same wording as the
mobile client, which is the same decision made once.

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant