From 0355167e02a710c0e40484592ac794810cde3922 Mon Sep 17 00:00:00 2001 From: Christophe Besson Date: Fri, 14 Aug 2026 23:52:13 +0200 Subject: Notifications: one per conversation, none for your own messages MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Four things were wrong, and they compounded: a busy chat produced one row per message, muting a group did nothing at all, there was no way to clear the list, and the one person guaranteed to know about a message — its author — was told about it. The author bug was a name mismatch across two processes. The node sent chat_notify without saying who wrote the message, so the hub used the node's own token subject, which is the operator's account. The skip therefore matched the operator and no one else: everybody was notified of their own messages, and the operator was notified of nobody's. The node now names the author and the hub reads that field. Muting lived in the browser's localStorage and nothing ever read it, so the checkbox was decoration. It is a column on group_members now, checked where the notification is created — a notification nobody wants is not written at all. Chat keeps a single row per (user, kind, group) whose date moves and whose read flag clears, so a conversation is one line saying when it last spoke. Clicking it opens the group and dismisses it; joining a group dismisses its invitation; and DELETE /v1/notifications clears the lot. The hub deploy now runs alembic. create_all() only creates missing tables, so group_members.muted never arrived on the running hub and /v1/groups/mine answered 500 — worth catching in the script rather than in a browser. Verified end to end against the deployed hub and node: the author receives nothing, the other member receives exactly one, carrying its group_id. Co-Authored-By: Claude Opus 5 --- packages/meshbay-hub/src/meshbay_hub/api/revocation.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) (limited to 'packages/meshbay-hub/src/meshbay_hub/api/revocation.py') diff --git a/packages/meshbay-hub/src/meshbay_hub/api/revocation.py b/packages/meshbay-hub/src/meshbay_hub/api/revocation.py index 2e3323c..58ebf50 100644 --- a/packages/meshbay-hub/src/meshbay_hub/api/revocation.py +++ b/packages/meshbay-hub/src/meshbay_hub/api/revocation.py @@ -122,6 +122,9 @@ async def _handle_chat_notify(group_id: str, sender_name: str, sender_user_id: s db, uid, "chat_message", f"{sender_name or 'Someone'} posted in {group.name}", link=f"#/group/{group_id}", + group_id=group_id, + # One line per conversation, moved to when it last spoke. + aggregate=True, ) await db.commit() except Exception as e: @@ -250,7 +253,12 @@ async def node_websocket(ws: WebSocket): asyncio.ensure_future(_handle_chat_notify( msg.get("group_id", ""), msg.get("sender_name", ""), - decoded.get("sub", ""), + # The author, as the node authenticated them — not + # decoded["sub"], which is the machine's own account and + # made this filter miss everyone except the operator. A node + # that lied here could only suppress one notification, which + # is the same power it has by not sending the message at all. + msg.get("sender_user_id", ""), )) except WebSocketDisconnect: -- cgit v1.2.3