Skip to content

fix(chat): keep a just-joined group when an older group feed lands - #1240

Merged
bmc08gt merged 2 commits into
mainfrom
fix/group-feed-stale-eviction
Nov 9, 2026
Merged

bmc08gt merged 2 commits into
mainfrom
fix/group-feed-stale-eviction

Conversation

@bmc08gt

@bmc08gt bmc08gt commented Nov 9, 2026

Copy link
Copy Markdown
Collaborator

After tapping Join, the user sometimes lands on an empty conversation titled "Flipcash User"; joining again works.

Cause

  • ConversationController.reconcileFeeds() fetches GetGroupChatFeed, then waits for the other feeds and unread counts before applyFeed; a late feed applies on its own. loadGroupFeed() is the same shape. The snapshot can predate a join.
  • If JoinChat finishes in that window, seatJoined upserts the group and calls store.setMembership(true).
  • When the stale feed lands, ConversationStore.setGroupFeed computes departed = joinedGroupIDs.subtracting(feed), which includes the new group. It is removed from conversations, and replaceGroupFeed(..., departed:) deletes it from the DB.
  • The screen then falls back to the placeholder name, has no group, and the gate stays .undetermined. Its hydrate task runs once, so nothing restores it.
  • Common trigger: an invite link tapped from another app. Foregrounding runs loadFeed while the user taps Join. seatCreatedGroup has the same window.

Fix

  • ConversationStore.setMembership(true, ...) stamps the group with a generation; groupFeedMark exposes the current one.
  • setGroupFeed(_:requestedAt:) keeps groups seated after the mark: they stay in the store, stay joined, and are excluded from departed.
  • reconcileFeeds() and loadGroupFeed() read the mark before fetching and pass it on apply, including the late-arrival path.
  • A group missing from a feed requested after it was seated is still removed.

Not changed: re-hydrating the screen when its conversation drops out of the store. The store fix removes the trigger, and a second restore path adds risk to the screen's load flow.

After tapping Join, the user could land on an empty "Flipcash User"
conversation. A group feed requested before the join can be applied
after it; `setGroupFeed` read the group's absence as a departure,
removed it from the store and deleted it from the DB, so the open
screen could not find its conversation.

The store now stamps each membership it seats (join, create, roster
join) with a generation. The controller reads `groupFeedMark` before a
group feed is requested and passes it to `setGroupFeed(_:requestedAt:)`,
which keeps groups seated after that mark, still joined and out of
`departed`. A group missing from a feed requested after it was seated
is still dropped.
@bmc08gt bmc08gt self-assigned this Nov 9, 2026
@bmc08gt
bmc08gt merged commit ae1d551 into main Nov 9, 2026
4 checks passed
bmc08gt added a commit that referenced this pull request Nov 9, 2026
…#1241)

Follows #1240. Leaving a group while a group feed was in flight was
undone when that feed landed: `setMembership(false)` dropped the group's
stamp, so `setGroupFeed` saw the stale listing, marked the user joined
again, and `replaceGroupFeed` wrote membership=true to the DB.

The stamp now records every membership change, join or leave. For a
group changed after a feed's `requestedAt` mark, local membership wins:
a join is kept, a leave stays un-joined. The left group's row is kept so
the screen the user left from can still render its gate, and the
controller records only still-joined groups through `replaceGroupFeed`.
A feed requested after the change is trusted fully.
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