Private
Public Access
Fix chat/room ordering that could differ between devices (#45)
Two independent gaps, both fixed since the report was ambiguous about which "chats" meant: - ChatPane.tsx concatenated history (REST-fetched) and live (WS-pushed) without sorting, so anything that could desync receipt order from send order -- a rejoin/resync racing a still-in-flight WS message, which opening the same room on another device triggers directly via a fresh socket connection -- could render messages out of chronological order. Now sorted by created_at (stable sort, so same-timestamp messages keep their relative order). - list_member_rooms/list_open_rooms/list_recent_messages ordered by created_at alone, with no secondary tiebreaker. Postgres doesn't guarantee a stable order for tied rows across separate query executions, so two rooms/messages sharing an identical timestamp (a real possibility -- rapid sends, bulk-created rooms) could come back in a different order on two separate fetches, i.e. two devices. Added id as a secondary sort key everywhere this showed up. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -65,7 +65,12 @@ async def list_recent_messages(
|
||||
select(Message)
|
||||
.where(Message.room_id == room_id)
|
||||
.options(selectinload(Message.user), selectinload(Message.file))
|
||||
.order_by(Message.created_at.desc())
|
||||
# Secondary key on the primary key -- two messages can share the
|
||||
# same created_at (rapid sends, e.g. from different clients or a
|
||||
# webhook), and without a tiebreaker Postgres isn't obligated to
|
||||
# return them in the same relative order on every call, which can
|
||||
# look like messages swapping places between fetches/devices.
|
||||
.order_by(Message.created_at.desc(), Message.id.desc())
|
||||
.limit(limit)
|
||||
)
|
||||
messages = list(result.scalars().all())
|
||||
|
||||
@@ -71,7 +71,7 @@ async def list_open_rooms(db: AsyncSession, user_id: uuid.UUID) -> list[tuple[Ro
|
||||
select(Room)
|
||||
.where(Room.is_private.is_(False), Room.is_archived.is_(False))
|
||||
.options(selectinload(Room.memberships))
|
||||
.order_by(Room.created_at)
|
||||
.order_by(Room.created_at, Room.id)
|
||||
)
|
||||
rooms = result.scalars().all()
|
||||
return [
|
||||
@@ -108,7 +108,13 @@ async def list_member_rooms(
|
||||
)
|
||||
.join(RoomMembership, RoomMembership.room_id == Room.id)
|
||||
.where(RoomMembership.user_id == user_id)
|
||||
.order_by(Room.created_at)
|
||||
# A secondary key on the primary key -- without it, Postgres has no
|
||||
# obligation to return two same-instant rooms (a plausible tie:
|
||||
# bulk-created/migrated rooms, or just two created in quick
|
||||
# succession) in the same order on every call, which without a
|
||||
# stable order can visibly reshuffle the sidebar between one
|
||||
# device's fetch and another's.
|
||||
.order_by(Room.created_at, Room.id)
|
||||
)
|
||||
return [
|
||||
(room, role, last_message_at is not None and last_message_at > last_read_at, has_mention)
|
||||
|
||||
Reference in New Issue
Block a user