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:
2026-08-17 11:52:29 -06:00
co-authored by Claude Sonnet 5
parent c99a07cae1
commit a0e1565097
5 changed files with 100 additions and 5 deletions
+8 -2
View File
@@ -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)