fix: live presence on freshly-friended remotes + green dot in same session
Two follow-on bugs from the initial S2S presence rollout:
(1) New friend stuck offline until they reload: presence_update fires only on
transitions, so a remote user already online when their stub is created
locally never receives a relay event seeding their actual status. The
stub defaulted to 'offline' at creation and stayed there until the next
transition. Fix: extend FederationRelayProfileSnapshot +
FederationUserLookupProfile with status. Sender-side buildProfileSnapshot,
getDmParticipants, and lookup endpoint responses populate it for native
users only (replicated stubs hold stale status owned elsewhere).
resolveOrCreateReplicatedUser uses hints.status to seed the new row's
status column. Threaded through every call site (DM participants, group
bootstrap, friend events, ownership transfer). Stub backfill worker also
heals existing rows whose status was stuck at 'offline' from creation.
(2) 'Online' text updates but green avatar dot stays grey on the same page:
spaceStore.updateMemberPresence patches members[] (which feeds space UIs)
but never patches userViews — the cache useCanonicalUserView reads from.
The Avatar in FriendItem reads canonical.status; the text reads
friend.status (socialStore). Two sources, one stale until full
user_updated arrives. Fix: updateMemberPresence now mirrors status into
matching userViews entries, so canonical-view consumers re-render with
fresh status the moment the WS event lands.
This commit is contained in:
@@ -346,6 +346,7 @@ export function getDmParticipants(dmChannelId: string): FederationRelayParticipa
|
||||
displayName: schema.users.displayName,
|
||||
avatar: schema.users.avatar,
|
||||
avatarColor: schema.users.avatarColor,
|
||||
status: schema.users.status,
|
||||
})
|
||||
.from(schema.dmMembers)
|
||||
.innerJoin(schema.users, eq(schema.dmMembers.userId, schema.users.id))
|
||||
@@ -362,6 +363,9 @@ export function getDmParticipants(dmChannelId: string): FederationRelayParticipa
|
||||
displayName: m.displayName ?? null,
|
||||
avatar: m.avatar ?? null,
|
||||
avatarColor: m.avatarColor ?? null,
|
||||
// Only carry presence for native participants — replicated stubs hold
|
||||
// stale status owned by their home; emitting it would flap remote UIs.
|
||||
status: !m.homeInstance ? (m.status as 'online' | 'idle' | 'dnd' | 'offline' | null) : null,
|
||||
},
|
||||
}));
|
||||
}
|
||||
|
||||
@@ -74,10 +74,17 @@ export async function backfillStubUsernamesForPeer(peerOrigin: string): Promise<
|
||||
|
||||
// Fill displayName from result.profile if the stub has none, mirroring the
|
||||
// displayName ?? username fallback applied at hydrate / profile_update time.
|
||||
const updates: { username: string; displayName?: string } = { username: newUsername };
|
||||
const updates: { username: string; displayName?: string; status?: 'online' | 'idle' | 'dnd' | 'offline' } = { username: newUsername };
|
||||
if (!stub.displayName) {
|
||||
updates.displayName = result.profile.displayName ?? result.username;
|
||||
}
|
||||
// Heal status too — same root issue (stub was seeded offline at creation
|
||||
// because the wire snapshot pre-dated the status field). Only overwrite
|
||||
// when the lookup tells us something specific; keep the stub's current
|
||||
// value otherwise.
|
||||
if (result.profile.status && result.profile.status !== stub.status) {
|
||||
updates.status = result.profile.status;
|
||||
}
|
||||
|
||||
db.update(schema.users)
|
||||
.set(updates)
|
||||
|
||||
Reference in New Issue
Block a user