Skip to content

Stop holding hidden conversations (GRYT-1379) - #217

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

sivert-io merged 1 commit into
mainfrom
claude/GRYT-1379-hide-conversations

Conversation

@sivert-io

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

Copy link
Copy Markdown
Member

Hiding a conversation was a row on the server. dm:setHidden set conversation_members.hidden_at, listConversationsForUser filtered those rows out, and a new message cleared it for everybody. Sivert wants it on the device instead: "it should purely be a visual thing", and "i dont want the server to hold it".

So the server lets go of it, and the client does the hiding on its own: Gryt-chat/client#649. The socket reference documented both events, so Gryt-chat/docs#129 drops them. Task: https://tasks.sivert.io/tasks/1456

What changes

  • listConversationsForUser stops filtering on hidden_at, so the list holds every conversation again.
  • dm:setHidden and the dm:hidden event are gone, along with setConversationHidden and clearConversationHidden.
  • dm:open no longer unhides, and chat:send no longer unhides everybody in the conversation. The first message in a new conversation still announces it, which is what that block was doing as well.
  • A one-shot migration clears every hidden_at the old code wrote, stamped in schema_meta so it runs once.

Without that clear, anything hidden before today would stay filtered out for good: the new client can only draw what it is sent, and it is never sent those rows.

The column stays

conversation_members.hidden_at is kept, unused. Dropping it is a rebuild of the table in SQLite, and an older binary rolling back would then write to a column that isn't there. Keeping it costs one TEXT column per membership row and nothing reads it.

What to look at

Blocking. user:block used to call hideConversationsBetween, which took the blocked person's conversation off the blocker's list through the same column. With the filter gone that write does nothing, so leaving it in would be a line that reads as working and isn't. I deleted it, and the client hides the row locally when a block lands instead. Same effect, and it can be found again under Hidden rather than being gone for good. That's the part I'm least sure about, since it's the one place the two halves have to agree.

One thing I expected to lose and didn't: today a blocked person's message already unhides the blocker's row, because clearConversationHidden unhid everybody in the conversation without asking who. The client rule has the same hole, so blocking behaves as it did.

chat:send. The wasEmpty announce loop is the same set it always sent; it just no longer unions in whoever clearConversationHidden reported.

The rest is shape rather than behaviour.

Tested

yarn test (1454 tests), yarn test:examples, yarn build, npx eslint ., the comment-length and self-hosted-config checks, all green.

The tests that covered hiding now cover letting go of it: dm:setHidden is not registered, a conversation still carrying hidden_at is listed, and the migration clears the column once and leaves the marker behind so a second run doesn't.

Also run against a real client: a throwaway server built from this branch, the client's Playwright suite on the other end, two accounts hiding and unhiding conversations between them.

The reference check

reference / Compare the reference pages against the source is red here and stays red until Gryt-chat/docs#129 lands: check-socket-coverage.mjs fails hard on an event the page describes and the server doesn't have. The docs PR is green on its own, so it goes first. verify is green here.

Accepted

Once a server updates, Hide in an older client does nothing. It writes hidden_at that nothing reads, and the row stays in the list.

Don't merge this one. It's yours to read first.

🤖 Generated with Claude Code

Hiding a conversation was a row on the server. `dm:setHidden` set
`conversation_members.hidden_at`, `listConversationsForUser` filtered those rows
out, and a new message cleared it for everybody. Sivert wants it on the device
instead. It's a visual thing, and the server shouldn't hold it.

So the list stops filtering on `hidden_at`, and `dm:setHidden` and the
`dm:hidden` event are gone. A one-shot migration clears every value the old code
wrote. Without that clear, anything hidden before today stays filtered out and
the new client can never draw it, since it can't show what it isn't sent.

The column stays, unused. Drop it and an older binary rolling back writes to a
column that isn't there. Keeping it costs one TEXT column per membership row.

Blocking used the same column to take the blocked person's conversation off the
blocker's list. With the filter gone that write does nothing, so it goes too,
and the client hides the row locally instead. Same effect, and you can find it
again under Hidden rather than it being gone for good.

Hide in a client older than this stops doing anything once a server updates.

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 0b56057 into main Sep 23, 2026
6 of 7 checks passed
@sivert-io
sivert-io deleted the claude/GRYT-1379-hide-conversations branch September 23, 2026 11:16
sivert-io added a commit to Gryt-chat/client that referenced this pull request Sep 23, 2026
* Hide a conversation on this device (GRYT-1379)

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]>

* Screenshots for the GRYT-1379 PR

Co-Authored-By: Claude Opus 5 <[email protected]>

* Take the screenshots back out of the tree (GRYT-1379)

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]>

---------

Co-authored-by: Claude Opus 5 <[email protected]>
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