Skip to content

Messages should sort in strict local receive order, not the sender's embedded clock #71

Description

@MrSurly

Summary

Messages are currently ordered (in every query in lib/database/daos/messages_dao.dart -- at least 14 separate call sites, covering channel messages, DMs, and likely other message-derived views) strictly by Messages.timestamp, which is the sender's own device clock value embedded in the message payload at compose time (the first 4 bytes of the over-the-air plaintext, before encryption). This value is never validated or bounded.

Concrete proof this is broken

In a live Public channel on our test setup, a device ("Connor123ttt") sent several messages with timestamps of 2093-06-06 -- a broken clock, 67 years in the future. Since watchMessagesByChannelForCompanion (the query the chat screen uses) sorts ascending by this untrusted field, those messages sorted to the very bottom of the list -- exactly where the UI auto-scrolls to, expecting "most recent." This buried genuinely recent messages (e.g. one from ~10 minutes prior with a correct 2026-07-19 timestamp) further up the scroll, making them easy to miss entirely.

Any single device with a wrong clock -- broken, unset, intentionally spoofed, or just badly drifted -- can corrupt the perceived message order for everyone in a shared channel. This isn't a sync bug; the messages are received and stored correctly. It's purely a display/ordering bug.

Desired behavior

  • Messages must sort in strict, monotonic local receive order -- i.e. the order this device actually received them, immune to any sender's clock.
  • Add a receivedAt column to Messages, set once at insert time from DateTime.now() (the device running the app's own clock), and use it (not the existing timestamp column) for all ordering in messages_dao.dart.
  • receivedAt should also become the primary timestamp displayed on message bubbles (currently channel_chat_screen.dart/direct_message_screen.dart show Messages.timestamp via formatMessageTime).
  • Keep the existing sender-embedded timestamp field as-is -- don't discard it. It's still useful (e.g. to show "sent" vs "received" if they diverge meaningfully, or for diagnostics) as a secondary/reference value, just not for ordering or as the primary displayed time.

Scope note

This touches a schema migration (new column, all-messages backfill isn't really meaningful since we don't know true historical receive order for already-stored rows -- likely just default backfilled rows to their existing timestamp or insertion rowid order, whichever is simpler, since this only matters going forward for correctness), the DAO query layer (many call sites), and the two chat screens' display of message time.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions