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
+6 -1
View File
@@ -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())