From 1782c3db7ba201dad4b810f3e6c086170d024570 Mon Sep 17 00:00:00 2001 From: Narasimha-sc <166327228+Narasimha-sc@users.noreply.github.com> Date: Sat, 29 Aug 2026 18:12:13 +0000 Subject: [PATCH] core: fix unread support items left behind when pending member leaves group xGrpLeave deletes the leaving member's support chat, and every chat item in it, and then creates the "member left" item - but it passes the pre-leave member value to mkGroupChatScope. A member pending approval or review still reads as pending there, so the item is written back into the support scope that was just deleted, and mkMemberSupportChatInfo and updateChatTsStats recreate the support chat around it. That item is unread and counts towards the profile unread total, as the group query in getUsersInfo has no group scope filter. The member is now GSMemLeft, so the UI filters them out of the member support chat list and the item can never be opened: the count stays until the whole profile is marked read. Pass the member with the status it was just given, so the event is created in the group scope, as it already is when a member is removed. --- src/Simplex/Chat/Library/Subscriber.hs | 8 ++++--- tests/ChatTests/Groups.hs | 32 ++++++++++++++++++++++++++ 2 files changed, 37 insertions(+), 3 deletions(-) diff --git a/src/Simplex/Chat/Library/Subscriber.hs b/src/Simplex/Chat/Library/Subscriber.hs index 79ce572785..54fba0ba68 100644 --- a/src/Simplex/Chat/Library/Subscriber.hs +++ b/src/Simplex/Chat/Library/Subscriber.hs @@ -3680,11 +3680,13 @@ processAgentMessageConn cxt user@User {userId} corrId agentConnId agentMessage = -- member record is not deleted to allow creation of "member left" chat item gInfo' <- updateMemberRecordDeleted user gInfo m GSMemLeft gInfo'' <- updatePublicGroupData user gInfo' - unless (muteEventInChannel gInfo'' m) $ do - (gInfo''', m', scopeInfo) <- mkGroupChatScope gInfo'' m + -- support chat was deleted with the member record above, so the item has to be created in the group scope + let mLeft = m {memberStatus = GSMemLeft, supportChat = Nothing} + unless (muteEventInChannel gInfo'' mLeft) $ do + (gInfo''', m', scopeInfo) <- mkGroupChatScope gInfo'' mLeft (ci, cInfo) <- saveRcvChatItemNoParse user (CDGroupRcv gInfo''' scopeInfo m') msg brokerTs (CIRcvGroupEvent RGEMemberLeft) groupMsgToView cInfo ci - toView $ CEvtLeftMember user gInfo''' m' {memberStatus = GSMemLeft} msgSigned + toView $ CEvtLeftMember user gInfo''' m' msgSigned pure $ memberEventDeliveryScope m xGrpDel :: GroupInfo -> GroupMember -> RcvMessage -> UTCTime -> CM () diff --git a/tests/ChatTests/Groups.hs b/tests/ChatTests/Groups.hs index 2c01f379ad..bb9f1a12e0 100644 --- a/tests/ChatTests/Groups.hs +++ b/tests/ChatTests/Groups.hs @@ -128,6 +128,7 @@ chatGroupTests = do it "accept member - only moderators review" testGLinkReviewMember it "accept member - host approval, then moderators review" testGLinkApproveThenReviewMember it "delete pending approval member" testGLinkDeletePendingApprovalMember + it "pending approval member leaves the group" testGLinkPendingApprovalMemberLeaves it "admin that joined via link introduces member for moderator review" testGLinkReviewIntroduce describe "group link connection plan" $ do it "ok to connect; known group" testPlanGroupLinkKnown @@ -3621,6 +3622,37 @@ testGLinkDeletePendingApprovalMember = where cfg = testCfg {chatHooks = defaultChatHooks {acceptMember = Just (\_ _ _ -> pure $ Right (GAPendingApproval, GRObserver))}} +testGLinkPendingApprovalMemberLeaves :: HasCallStack => TestParams -> IO () +testGLinkPendingApprovalMemberLeaves = + testChatCfg3 cfg aliceProfile bobProfile cathProfile $ + \alice bob cath -> do + createGroup2 "team" alice bob + + alice ##> "/create link #team" + gLink <- getGroupLink alice "team" GRMember True + cath ##> ("/c " <> gLink) + cath <## "connection request sent!" + alice <## "cath (Catherine): accepting request to join group #team..." + concurrentlyN_ + [ alice <## "#team: cath connected and pending approval, use /_accept member #1 3 to accept member", + do + cath <## "#team: joining the group..." + cath <## "#team: you joined the group, pending approval" + ] + + cath ##> "/leave #team" + concurrentlyN_ + [ do + cath <## "#team: you left the group" + cath <## "use /d #team to delete the group", + alice <## "#team: cath left the group" + ] + + alice ##> "/member support chats #team" + alice <## "members require attention: 0" + where + cfg = testCfg {chatHooks = defaultChatHooks {acceptMember = Just (\_ _ _ -> pure $ Right (GAPendingApproval, GRObserver))}} + testGLinkReviewIntroduce :: HasCallStack => TestParams -> IO () testGLinkReviewIntroduce = testChat5 aliceProfile bobProfile cathProfile danProfile eveProfile $