Skip to content

Hide a conversation on this device (GRYT-1379) - #649

Merged
sivert-io merged 3 commits into
mainfrom
claude/GRYT-1379-hide-conversations
Sep 23, 2026
Merged

sivert-io merged 3 commits into
mainfrom
claude/GRYT-1379-hide-conversations

Conversation

@sivert-io

@sivert-io sivert-io commented Sep 23, 2026 •

Copy link
Copy Markdown
Member

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_at column 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/1456

The Messages list with one conversation hidden: a quiet Hidden row with a count of 1 and a chevron under the remaining conversation, and a toast in the corner saying Hid Aurora-933264 with an Undo

The same list with the Hidden group unfolded, the hidden conversation drawn dimmed under it

A hidden conversation open and selected: the row is highlighted inside the Hidden group and its messages fill the pane

How 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 markChannelUnread calls sit on chat:new (useChat.ts:409, useSocketEvents.ts:455), the same event that moves last_message_at in useDirectMessages, 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 useDirectoryUnread sums 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:block no 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.tsx is not drawn anywhere. Every call site passes showDirectMessages={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/mobile does 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 e2e and every check script in ci.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

sivert-io and others added 3 commits September 23, 2026 12:29
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]>
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]>
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]>
@sivert-io
sivert-io merged commit 181605d into main Sep 23, 2026
7 checks passed
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