From 0cb02ee18fd2728998d048d1b52e19d8ff460a43 Mon Sep 17 00:00:00 2001 From: Jakub <106778416+MarekZegare4@users.noreply.github.com> Date: Thu, 3 Sep 2026 12:37:57 +0200 Subject: [PATCH] fix(ui): DM contact picker missed real contacts (off-by-MAX_ANON_CONTACTS) getContactByIdx() indexes the raw contacts[] table directly, whose first MAX_ANON_CONTACTS (8) slots are reserved for anon requests -- getNumContacts() already excludes them from its count, so real contacts start at index MAX_ANON_CONTACTS, not 0 (NearbyScreen.h's own contact scan already applies this offset; its comment documents why). MessagesScreen.h's buildContactList() didn't, so its loop only ever read the reserved anon slots (empty name, type 0) for any total at or under MAX_ANON_CONTACTS -- e.g. a device with exactly one known contact would show "SELECT CONTACT" / "No favourites" with an empty list forever, regardless of the dm_show_all/fav_only setting or that contact's own favourite flag. Reproduced live in the browser sim: a fresh companion_radio instance with one real ADV_TYPE_CHAT contact showed nothing until this fix. Also fixed _sorted[]'s stored index (was the bare loop counter, needed to be the raw table index every other call site in the file already assumes) and the same missing-offset bug in BotScreen.h's room-contact counter and MessageHistory.h's pub_key-prefix contact lookup. Co-Authored-By: Claude Sonnet 5 --- examples/companion_radio/ui-new/BotScreen.h | 8 +++++++- .../companion_radio/ui-new/MessageHistory.h | 5 ++++- .../companion_radio/ui-new/MessagesScreen.h | 20 +++++++++++++++++-- 3 files changed, 29 insertions(+), 4 deletions(-) diff --git a/examples/companion_radio/ui-new/BotScreen.h b/examples/companion_radio/ui-new/BotScreen.h index f73bd921..f8e6b8d9 100644 --- a/examples/companion_radio/ui-new/BotScreen.h +++ b/examples/companion_radio/ui-new/BotScreen.h @@ -96,8 +96,14 @@ class BotScreen : public UIScreen { _num_rooms = 0; ContactInfo ci; int total = the_mesh.getNumContacts(); + // +MAX_ANON_CONTACTS: getContactByIdx() indexes the raw contacts[] table, + // whose first MAX_ANON_CONTACTS slots are reserved anon-request entries + // that getNumContacts() already excludes from its count (see + // NearbyScreen.h's own contact scan, and MessagesScreen.h's + // buildContactList(), for the same offset) -- without it this underrode + // real room-server contacts by up to MAX_ANON_CONTACTS. for (int i = 0; i < total; i++) - if (the_mesh.getContactByIdx(i, ci) && ci.type == ADV_TYPE_ROOM) _num_rooms++; + if (the_mesh.getContactByIdx(MAX_ANON_CONTACTS + i, ci) && ci.type == ADV_TYPE_ROOM) _num_rooms++; } // Header as a circular tab bar (shared geometry — see TabBar.h). `right_reserve` diff --git a/examples/companion_radio/ui-new/MessageHistory.h b/examples/companion_radio/ui-new/MessageHistory.h index c028eda0..096a8c2f 100644 --- a/examples/companion_radio/ui-new/MessageHistory.h +++ b/examples/companion_radio/ui-new/MessageHistory.h @@ -388,9 +388,12 @@ private: // Look up a contact by 4-byte pub_key prefix (as stored in DmHistEntry). bool contactByPrefix(const uint8_t* prefix, ContactInfo& out) const { int total = the_mesh.getNumContacts(); + // +MAX_ANON_CONTACTS: see MessagesScreen.h's buildContactList() for why + // getContactByIdx() needs this offset (raw table index, not the + // anon-excluded logical count getNumContacts() returns). for (int i = 0; i < total; i++) { ContactInfo c; - if (!the_mesh.getContactByIdx(i, c)) continue; + if (!the_mesh.getContactByIdx(MAX_ANON_CONTACTS + i, c)) continue; if (memcmp(c.id.pub_key, prefix, 4) == 0) { out = c; return true; } } return false; diff --git a/examples/companion_radio/ui-new/MessagesScreen.h b/examples/companion_radio/ui-new/MessagesScreen.h index 3f25a94b..220bdac2 100644 --- a/examples/companion_radio/ui-new/MessagesScreen.h +++ b/examples/companion_radio/ui-new/MessagesScreen.h @@ -694,8 +694,20 @@ class MessagesScreen : public UIScreen { // task stack for one local array. uint8_t keys[MAX_CONTACTS]; bool fav_only = rooms ? (p && p->room_fav_only) : !(p && p->dm_show_all); + // getContactByIdx() indexes the RAW contacts[] table, whose first + // MAX_ANON_CONTACTS slots are reserved for anon requests (see + // BaseChatMesh::resetContacts()/ContactsIterator); getNumContacts() + // already excludes them from the count, so the real contacts start at + // MAX_ANON_CONTACTS, not 0 -- same offset NearbyScreen.h's own contact + // scan already applies. Without it, this loop only ever read the + // reserved anon slots (empty name, type 0) for the first + // MAX_ANON_CONTACTS iterations and, for any total at or under that count, + // NEVER reached a real contact at all -- e.g. a device with exactly one + // known contact would show "No favourites" with an empty list forever, + // regardless of the dm_show_all/fav_only setting or that contact's own + // favourite flag. for (int i = 0; i < total; i++) { - if (!the_mesh.getContactByIdx(i, c)) continue; + if (!the_mesh.getContactByIdx(MAX_ANON_CONTACTS + i, c)) continue; if (c.type != (rooms ? ADV_TYPE_ROOM : ADV_TYPE_CHAT)) continue; bool fav = (c.flags & 0x01) != 0; if (fav_only && !fav) continue; @@ -703,7 +715,11 @@ class MessagesScreen : public UIScreen { if (k > 127) k = 127; if (fav && !(p && p->fav_sort_off)) k += 128; keys[_num_contacts] = k; - _sorted[_num_contacts++] = i; + // Store the RAW table index (not the bare loop counter) -- every other + // call site in this file (render()'s mesh_idx, toggleFav(), the + // context-menu handlers, ...) reads _sorted[...] straight into + // getContactByIdx() with no offset of its own. + _sorted[_num_contacts++] = MAX_ANON_CONTACTS + i; } // Descending insertion sort; rows with key 0 keep their contact-table order. for (int i = 1; i < _num_contacts; i++) {