fix(companion): show the sender's own timestamp for synced/queued messages

DM, room and channel history always stamped a message with receipt time
(rtc_clock.getCurrentTime()), even though the sender's real timestamp was
already available (and, for DMs/rooms, already threaded through to
addDMMsg -- just never used for display). Live-received messages hid this
because receipt lags origination by only seconds, but a room-sync replay or
an offline-queued message held by a repeater can arrive long after it was
sent, so a burst of backlog messages all showed as "just now".

storeDMMsg() now prefers msg_ts (falling back to receipt time only when
unknown). addChannelMsg() gained a timestamp parameter, threaded from
onChannelMessageRecv() down through AbstractUITask/UITask, with the same
fallback. The DM dedup check and outgoing-message timestamps are unaffected
(they use msg_ts directly, already correct).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
MarekZegare4
2026-06-28 11:00:45 +02:00
co-authored by Claude Sonnet 4.6
parent 6a394e2d7a
commit 7a5247d5d4
5 changed files with 15 additions and 9 deletions
+1 -1
View File
@@ -65,7 +65,7 @@ public:
virtual void msgRead(int msgcount) = 0; virtual void msgRead(int msgcount) = 0;
virtual void newMsg(uint8_t path_len, const char* from_name, const char* text, int msgcount, uint8_t contact_type = 0, const uint8_t* pub_key = nullptr) = 0; virtual void newMsg(uint8_t path_len, const char* from_name, const char* text, int msgcount, uint8_t contact_type = 0, const uint8_t* pub_key = nullptr) = 0;
virtual void notify(UIEventType t = UIEventType::none) = 0; virtual void notify(UIEventType t = UIEventType::none) = 0;
virtual void addChannelMsg(uint8_t channel_idx, const char* text) {} virtual void addChannelMsg(uint8_t channel_idx, const char* text, uint32_t timestamp = 0) {}
virtual void addDMMsg(const uint8_t* pub_key, bool outgoing, const char* text, uint32_t sender_timestamp = 0) {} virtual void addDMMsg(const uint8_t* pub_key, bool outgoing, const char* text, uint32_t sender_timestamp = 0) {}
// A node shared its current position via a [LOC] message. pub_key is the // A node shared its current position via a [LOC] message. pub_key is the
// sender's key prefix for a verified DM share, or null for a channel share // sender's key prefix for a verified DM share, or null for a channel share
+1 -1
View File
@@ -721,7 +721,7 @@ void MyMesh::onChannelMessageRecv(const mesh::GroupChannel &channel, mesh::Packe
_serial->writeFrame(frame, 1); _serial->writeFrame(frame, 1);
} }
#ifdef DISPLAY_CLASS #ifdef DISPLAY_CLASS
if (_ui) _ui->addChannelMsg(channel_idx, text); if (_ui) _ui->addChannelMsg(channel_idx, text, timestamp);
if (_ui) _ui->notify(UIEventType::channelMessage); if (_ui) _ui->notify(UIEventType::channelMessage);
const char *channel_name = "Unknown"; const char *channel_name = "Unknown";
ChannelDetails channel_details; ChannelDetails channel_details;
@@ -398,7 +398,11 @@ class QuickMsgScreen : public UIScreen {
} }
memcpy(_dm_hist[pos].prefix, pub_key, 4); memcpy(_dm_hist[pos].prefix, pub_key, 4);
_dm_hist[pos].outgoing = outgoing ? 1 : 0; _dm_hist[pos].outgoing = outgoing ? 1 : 0;
_dm_hist[pos].timestamp = rtc_clock.getCurrentTime(); // Prefer the sender's own timestamp — a room-sync replay or an
// offline-queued message held by a repeater can arrive long after it was
// actually sent, so "now" would mislabel every backlog message as fresh.
// Fall back to receipt time only when the sender's timestamp is unknown.
_dm_hist[pos].timestamp = msg_ts ? msg_ts : rtc_clock.getCurrentTime();
strncpy(_dm_hist[pos].text, text, sizeof(DmHistEntry::text) - 1); strncpy(_dm_hist[pos].text, text, sizeof(DmHistEntry::text) - 1);
_dm_hist[pos].text[sizeof(DmHistEntry::text) - 1] = '\0'; _dm_hist[pos].text[sizeof(DmHistEntry::text) - 1] = '\0';
_dm_hist[pos].ack_status = (outgoing && ack_tag) ? ACK_PENDING : ACK_NONE; _dm_hist[pos].ack_status = (outgoing && ack_tag) ? ACK_PENDING : ACK_NONE;
@@ -677,8 +681,10 @@ public:
} }
// Returns the ring position the message was stored at, or -1 if rejected, so // Returns the ring position the message was stored at, or -1 if rejected, so
// the outgoing path can attach a relay seq to that exact entry. // the outgoing path can attach a relay seq to that exact entry. `timestamp`
int addChannelMsg(uint8_t ch_idx, const char* text) { // is the sender's own send time (0 = unknown/outgoing — use receipt time);
// see storeDMMsg() for why this matters for synced/queued backlog messages.
int addChannelMsg(uint8_t ch_idx, const char* text, uint32_t timestamp = 0) {
// Guard against bogus channel indices (e.g. findChannelIdx() returned -1 // Guard against bogus channel indices (e.g. findChannelIdx() returned -1
// and was cast to uint8_t → 255). Storing such an entry would burn a ring // and was cast to uint8_t → 255). Storing such an entry would burn a ring
// slot for a message that no visible channel can ever surface. // slot for a message that no visible channel can ever surface.
@@ -698,7 +704,7 @@ public:
_hist_head = (_hist_head + 1) % CH_HIST_MAX; _hist_head = (_hist_head + 1) % CH_HIST_MAX;
} }
_hist[pos].ch_idx = ch_idx; _hist[pos].ch_idx = ch_idx;
_hist[pos].timestamp = rtc_clock.getCurrentTime(); _hist[pos].timestamp = timestamp ? timestamp : rtc_clock.getCurrentTime();
strncpy(_hist[pos].text, text, sizeof(_hist[pos].text) - 1); strncpy(_hist[pos].text, text, sizeof(_hist[pos].text) - 1);
_hist[pos].text[sizeof(_hist[pos].text) - 1] = '\0'; _hist[pos].text[sizeof(_hist[pos].text) - 1] = '\0';
_hist[pos].relay_status = ACK_NONE; _hist[pos].relay_status = ACK_NONE;
+2 -2
View File
@@ -1628,9 +1628,9 @@ int UITask::getRecentDMContacts(uint8_t out[][NodePrefs::FAVOURITE_PREFIX_LEN],
return ((QuickMsgScreen*)quick_msg)->getRecentDMContacts(out, max); return ((QuickMsgScreen*)quick_msg)->getRecentDMContacts(out, max);
} }
void UITask::addChannelMsg(uint8_t channel_idx, const char* text) { void UITask::addChannelMsg(uint8_t channel_idx, const char* text, uint32_t timestamp) {
_last_notif_ch_idx = (int)channel_idx; _last_notif_ch_idx = (int)channel_idx;
((QuickMsgScreen*)quick_msg)->addChannelMsg(channel_idx, text); ((QuickMsgScreen*)quick_msg)->addChannelMsg(channel_idx, text, timestamp);
} }
int UITask::getChannelUnreadCount() const { int UITask::getChannelUnreadCount() const {
+1 -1
View File
@@ -261,7 +261,7 @@ public:
void stopMelody(); void stopMelody();
bool isMelodyPlaying(); bool isMelodyPlaying();
void showAlert(const char* text, int duration_millis); void showAlert(const char* text, int duration_millis);
void addChannelMsg(uint8_t channel_idx, const char* text) override; void addChannelMsg(uint8_t channel_idx, const char* text, uint32_t timestamp = 0) override;
void addDMMsg(const uint8_t* pub_key, bool outgoing, const char* text, uint32_t sender_timestamp = 0) override; void addDMMsg(const uint8_t* pub_key, bool outgoing, const char* text, uint32_t sender_timestamp = 0) override;
void onMsgAck(uint32_t ack_crc) override; void onMsgAck(uint32_t ack_crc) override;
void onChannelRelayed(uint32_t seq) override; void onChannelRelayed(uint32_t seq) override;