From 1f7f1455a2bccbf0fbd2b18b924988efb2325d84 Mon Sep 17 00:00:00 2001 From: Jakub <106778416+MarekZegare4@users.noreply.github.com> Date: Mon, 31 Aug 2026 22:44:32 +0200 Subject: [PATCH] refactor(mesh): extract addOwnChannelMsg() for the "Me: " mirror framing Four call sites (three bot reply paths, one app-originated-send mirror) each hand-built the same "Me: " + own_message=true framing that MessagesScreen relies on to render an outgoing bubble -- one of them (now fixed) had already drifted to the wrong prefix once. Centralizing it in AbstractUITask::addOwnChannelMsg() means a caller can no longer get the framing wrong. --- examples/companion_radio/AbstractUITask.h | 19 +++++++++++++++++ examples/companion_radio/MyMesh.cpp | 26 +++++++++++------------ examples/companion_radio/MyMeshBot.h | 21 +++--------------- 3 files changed, 35 insertions(+), 31 deletions(-) diff --git a/examples/companion_radio/AbstractUITask.h b/examples/companion_radio/AbstractUITask.h index 301831a4..7a019e07 100644 --- a/examples/companion_radio/AbstractUITask.h +++ b/examples/companion_radio/AbstractUITask.h @@ -5,6 +5,7 @@ #include #include #include +#include // MAX_TEXT_LEN, for addOwnChannelMsg() below #include #ifdef PIN_BUZZER @@ -101,6 +102,24 @@ public: virtual int addChannelMsg(uint8_t channel_idx, const char* text, uint32_t timestamp = 0, const uint8_t* path = nullptr, uint8_t path_len = 0, bool own_message = false) { return -1; } + // Convenience wrapper around addChannelMsg() for mirroring a channel post + // this device just sent itself (bot trigger/command reply, a !gps fix + // result, an app-originated send) into the on-device history. Always frames + // it with the literal "Me: " prefix -- the convention MessagesScreen uses + // (see its bubble-side check) to tell an outgoing post from an incoming + // one -- and always passes own_message=true, so a caller can't reintroduce + // the bug this replaced: three separate MyMeshBot.h call sites used to + // build ": " instead, which rendered the reply as an incoming + // bubble from a stranger who happened to share the device's own name. + // text_len < 0 (default) means text is null-terminated; otherwise only the + // first text_len bytes are used (a source buffer isn't always guaranteed + // to be null-terminated, e.g. the app-originated mirror). + int addOwnChannelMsg(uint8_t channel_idx, const char* text, int text_len = -1, uint32_t timestamp = 0) { + char buf[MAX_TEXT_LEN + 8]; // "Me: "(4) + text(MAX_TEXT_LEN) + margin + if (text_len < 0) snprintf(buf, sizeof(buf), "Me: %s", text); + else snprintf(buf, sizeof(buf), "Me: %.*s", text_len, text); + return addChannelMsg(channel_idx, buf, timestamp, nullptr, 0, true); + } // Arms the "relayed into mesh" tracker (a heard repeater rebroadcast) on the // entry at ring position pos, e.g. right after addChannelMsg for a channel // send this device just originated. seq: MyMesh::lastChannelRelaySeq(). diff --git a/examples/companion_radio/MyMesh.cpp b/examples/companion_radio/MyMesh.cpp index 55a7bd3f..2c06b86f 100644 --- a/examples/companion_radio/MyMesh.cpp +++ b/examples/companion_radio/MyMesh.cpp @@ -2028,22 +2028,22 @@ void MyMesh::handleCmdFrame(size_t len) { if (success && sendGroupMessage(msg_timestamp, channel.channel, _prefs.node_name, text, len - i)) { writeOKFrame(); #ifdef DISPLAY_CLASS - // Mirror this app-originated channel post into the on-device history, - // same "Me: " framing MessagesScreen::afterSend uses for an on-device - // compose -- otherwise the two queues drift and a post sent from the - // phone app never shows up if that channel is later opened on-device. - // text isn't guaranteed null-terminated (len - i is its real length, - // same bound sendGroupMessage above was just given), so bound the copy. + // Mirror this app-originated channel post into the on-device history + // (addOwnChannelMsg applies the same "Me: " framing + // MessagesScreen::afterSend uses for an on-device compose) -- + // otherwise the two queues drift and a post sent from the phone app + // never shows up if that channel is later opened on-device. text + // isn't guaranteed null-terminated (len - i is its real length, same + // bound sendGroupMessage above was just given), so bound the copy. + // own_message=true (inside addOwnChannelMsg): this is our own post, + // so it must never bump the channel's unread badge even though the + // device's own UI isn't necessarily showing this channel right now + // (unlike an on-device compose, which is always looking at the + // channel it just sent to). if (_ui) { - char entry[MAX_TEXT_LEN + 8]; // "Me: " + text int tlen = len - i; if (tlen > MAX_TEXT_LEN) tlen = MAX_TEXT_LEN; - snprintf(entry, sizeof(entry), "Me: %.*s", tlen, text); - // own_message=true: this is our own post, so it must never bump the - // channel's unread badge even though the device's own UI isn't - // necessarily showing this channel right now (unlike an on-device - // compose, which is always looking at the channel it just sent to). - int pos = _ui->addChannelMsg(channel_idx, entry, msg_timestamp, nullptr, 0, true); + int pos = _ui->addOwnChannelMsg(channel_idx, text, tlen, msg_timestamp); // Same "relayed into mesh" marker an on-device channel send arms (see // MessagesScreen::afterSend): sendGroupMessage above already went // through sendFloodScoped(GroupChannel&, ...), which calls diff --git a/examples/companion_radio/MyMeshBot.h b/examples/companion_radio/MyMeshBot.h index 77d1901d..7479e134 100644 --- a/examples/companion_radio/MyMeshBot.h +++ b/examples/companion_radio/MyMeshBot.h @@ -199,14 +199,7 @@ void MyMesh::tryBotReplyChannel(uint8_t channel_idx, const char* text, uint8_t h _bot_last_ch_reply_ms = millis(); _bot_reply_count++; #ifdef DISPLAY_CLASS - if (_ui) { - // "Me: " (not node_name) -- MessagesScreen recognises an outgoing bubble - // by that literal prefix (see computeBubbleBox's caller), same framing - // MessagesScreen::afterSend and the app-originated-post mirror use. - char with_sender[240]; // "Me: "(4) + expanded(200) + margin - snprintf(with_sender, sizeof(with_sender), "Me: %s", expanded); - _ui->addChannelMsg(channel_idx, with_sender, 0, nullptr, 0, true); // own_message: our own bot reply, never unread - } + if (_ui) _ui->addOwnChannelMsg(channel_idx, expanded); #endif } } @@ -499,11 +492,7 @@ bool MyMesh::tryBotChannelCommand(uint8_t channel_idx, const char* text, uint8_t _bot_last_ch_reply_ms = millis(); _bot_reply_count++; #ifdef DISPLAY_CLASS - if (_ui) { - char with_sender[240]; - snprintf(with_sender, sizeof(with_sender), "%s: %s", _prefs.node_name, out); - _ui->addChannelMsg(channel_idx, with_sender, 0, nullptr, 0, true); // own_message: our own bot reply, never unread - } + if (_ui) _ui->addOwnChannelMsg(channel_idx, out); #endif if (_locfix_requested) startLocFix(LOCFIX_DEST_CHANNEL, nullptr, channel_idx); applyPendingBotActions(); @@ -658,11 +647,7 @@ void MyMesh::sendLocFixResult(const char* msg) { _bot_last_ch_reply_ms = millis(); _bot_reply_count++; #ifdef DISPLAY_CLASS - if (_ui) { - char with_sender[240]; - snprintf(with_sender, sizeof(with_sender), "%s: %s", _prefs.node_name, msg); - _ui->addChannelMsg(_loc_fix.channel_idx, with_sender, 0, nullptr, 0, true); // own_message: our own bot reply, never unread - } + if (_ui) _ui->addOwnChannelMsg(_loc_fix.channel_idx, msg); #endif } } else { // LOCFIX_DEST_CONTACT -- DM or room, both go through sendMessage