Repository navigation
Hide a conversation on this device (GRYT-1379) - #649
Merged
Merged
Conversation
Hiding was the server's answer until now, so it followed you between devices and there was no way to see what you had hidden. Sivert wants it the other way round: "it should purely be a visual thing", and "i dont want the server to hold it". So it lives in localStorage, one entry per server and account, holding each conversation id and the moment it was hidden. Nothing is sent anywhere. Every read and write is wrapped, since storage throws in a private window, and an empty answer draws the whole list rather than a broken one. A conversation comes back when its `last_message_at` is newer than the moment it was hidden. That falls out of the list the server already sends, so a message that arrived while the app was shut brings it back the same as a live one, and the stored entry is dropped once it's back. The moment is clamped to now when it is read, or a clock that went backwards would hide a conversation for good. Under the Messages list there's a Hidden row with a count and a chevron, which exists only while something is hidden. It expands in place and draws those conversations back. A dimmed row opens and reads normally without unhiding, and its menu offers Show in list. Whether the group is open is remembered per device, and it opens by itself while the conversation being read is one of the hidden ones. Hiding is instant, with an Undo in a toast for five seconds and no confirm. It also marks the conversation read, so a badge cannot be left counting for a row nobody can see. Blocking used to take the conversation off your list on the server, through the same column. The server half of this drops that, so the client does it here when a block lands. The server half is Gryt-chat/server#217. Co-Authored-By: Claude Opus 5 <[email protected]>
Co-Authored-By: Claude Opus 5 <[email protected]>
They stay reachable by the commit that added them, which is what the pull request body links to. Co-Authored-By: Claude Opus 5 <[email protected]>
This was referenced Sep 23, 2026
sivert-io
added a commit
to Gryt-chat/docs
that referenced
this pull request
Sep 23, 2026
…YT-1379) (#129) `dm:setHidden` and `dm:hidden` are gone from the server: hiding a conversation is the client's own now, kept on the device and sent nowhere. The page said otherwise, which fails the socket coverage check on the server's pull request. Gryt-chat/server#217 and Gryt-chat/client#649. Co-authored-by: Claude Opus 5 <[email protected]>
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.
Hide a conversation in the Messages list without leaving it. Sivert asked for it on this device only: "it should purely be a visual thing", and "i dont want the server to hold it".
The server half, which lets go of the
hidden_atcolumn it used to hold, is Gryt-chat/server#217, and Gryt-chat/docs#129 takes the two retired events off the socket reference. Task: https://tasks.sivert.io/tasks/1456How it works
Where it's kept. One localStorage entry per server host and account, holding each conversation id and the moment it was hidden. Nothing is sent anywhere. Every read and write is wrapped in try/catch: storage throws in a private window, and an unreadable one reads as nothing hidden, which draws the whole list rather than a broken one.
What brings it back. A message newer than the moment it was hidden. That comes out of the list the server already sends, so one that arrived while the app was shut counts the same as a live one, and no new event was needed. The entry is dropped once it's back.
The stored moment is clamped to now when it's read. Without that, a clock that went backwards after somebody hid something leaves a timestamp in the future that no message can beat, and the conversation is hidden for good.
The group. A row under the list with the word Hidden, a count and a chevron, which exists only while something is hidden. It expands in place, and the rows under it are dimmed to 0.55, open and read normally, and carry Show in list in their menu. Whether it's open is remembered per device, and it opens by itself while the conversation being read is one of the hidden ones, so the selection is never a cursor nobody can see.
Hiding. The menu item keeps its name and gains a second line, "Only on this device. Comes back if they write." Instant, with an Undo in a toast for five seconds, no confirm.
About the unread badge
A hidden row can't carry one. Both
markChannelUnreadcalls sit onchat:new(useChat.ts:409,useSocketEvents.ts:455), the same event that moveslast_message_atinuseDirectMessages, so anything that would badge a hidden row un-hides it first.The one gap was hiding a conversation that was already unread: the count is per session and would have sat there on a row nobody could see, and
useDirectoryUnreadsums every conversation in the directory, so the rail's dot would have counted it too. Hiding now marks the conversation read, which closes that.Mentions are the server's count, not this one, so a mention that arrived before the hide still shows on the dimmed row in the group. That seemed better than a client-side write to somebody's read markers.
What to look at
Blocking.
user:blockno longer hides the conversation on the server, so this does it locally when a block lands (serverView.tsx,blockAndHide). Same effect as before. The two PRs have to land together or blocking stops tidying the list.Picking what to open. The Messages space opens the most recent conversation when nothing is selected. It now skips hidden ones, or hiding the one you were reading picked it straight back up — the server still sends it, and only this device is holding it back.
DirectMessageList.tsxis not drawn anywhere. Every call site passesshowDirectMessages={false}(GRYT-1120 moved conversations into their own space), so the per-server section is dead UI. I left it wired and only changed its second line to match. Removing it is GRYT-1382.The phone uses the same sidebar inside its sheet, so it gets all of this. The mobile app in
packages/mobiledoes not, and it stays behind: GRYT-1381.Tested
scripts/check-hidden-conversations.mjs, new and wired into CI: which conversations are hidden given what's stored and what the server sent, the unhide-by-timestamp rule, a clock that went backwards, a conversation nobody has written in, one account's list not reaching another's, storage that throws, and stored nonsense.e2e/tests/hidden-conversations.spec.ts, five tests against a real server and two accounts: hiding and Undo, the group unfolding and Show in list, coming back on a message both live and after the page was closed and reopened, the other person's list being untouched, and the row on a phone with the count at the edge and a long name cut short.yarn lint,yarn build,yarn tsc -p e2eand every check script inci.yml, green.All of it ran against a server built from Gryt-chat/server#217 rather than
server:latest, since the pair only makes sense together.Don't merge this one. It goes in with the server PR, and both are yours to read.
🤖 Generated with Claude Code