diff --git a/examples/companion_radio/NodePrefs.h b/examples/companion_radio/NodePrefs.h index 09ba91c9..545e8991 100644 --- a/examples/companion_radio/NodePrefs.h +++ b/examples/companion_radio/NodePrefs.h @@ -87,7 +87,7 @@ struct NodePrefs { // persisted to file uint16_t home_pages_mask; // bitmask of visible home pages (bit0=Clock..bit8=Shutdown); 0=all visible uint8_t bot_enabled; // 0=disabled, 1=DM bot active (responds to all DMs) uint8_t bot_channel_enabled; // 0=disabled, 1=channel bot active for bot_channel_idx - uint8_t bot_channel_idx; // channel index for channel bot + uint8_t bot_channel_idx; // channel index for channel bot [del→onChannelRemoved] char bot_trigger[64]; // DM trigger phrase (case-insensitive contains; "*" = any DM) char bot_reply_dm[140]; // auto-reply text for DM char bot_reply_ch[140]; // auto-reply text for channel @@ -100,7 +100,7 @@ struct NodePrefs { // persisted to file uint8_t buzzer_auto; // 0=manual (default), 1=auto-mute when BT connected struct DmNotifEntry { uint8_t prefix[4]; uint8_t state; }; // state: 0=default,1=muted,2=force-on static const int DM_NOTIF_TABLE_MAX = 16; - DmNotifEntry dm_notif[DM_NOTIF_TABLE_MAX]; // 16*5 = 80 bytes + DmNotifEntry dm_notif[DM_NOTIF_TABLE_MAX]; // 16*5 = 80 bytes [del→onContactRemoved] uint8_t dashboard_fields[3]; // 0=None,1=Batt V,2=Temp,3=Hum,4=Pres,5=GPS,6=Alt,7=Lux,8=CO2,9=Nodes,10=Msgs,11=Batt % uint32_t advert_auto_interval_sec; // periodic 0-hop advert with GPS: 0=off, else seconds // Second melody slot (same packing as ringtone_*) @@ -114,12 +114,12 @@ struct NodePrefs { // persisted to file // Advert sound filter: 0=all adverts, 1=zero-hop only uint8_t advert_sound_scope; // Per-channel melody override (2 bitmasks, 1 bit per channel) - uint64_t ch_notif_melody_set; // bit i = channel i has explicit melody + uint64_t ch_notif_melody_set; // bit i = channel i has explicit melody [del→onChannelRemoved] uint64_t ch_notif_melody_2; // bit i = use melody 2 (else melody 1, when set bit is set) // Per-DM melody table struct DmMelodyEntry { uint8_t prefix[4]; uint8_t slot; }; // slot: 0=global,1=melody1,2=melody2 static const int DM_MELODY_TABLE_MAX = 16; - DmMelodyEntry dm_melody[DM_MELODY_TABLE_MAX]; + DmMelodyEntry dm_melody[DM_MELODY_TABLE_MAX]; // [del→onContactRemoved] uint8_t use_lemon_font; // 0=default Adafruit font, 1=Lemon font (Unicode, pixel-accurate wrap) uint8_t display_rotation; // 0-3; only used on e-ink displays // Home screen page order: each byte = HomePageBit + 1. 0 terminates the list. @@ -138,7 +138,7 @@ struct NodePrefs { // persisted to file // Layout transposes between landscape (3×2) and portrait (2×3). static const uint8_t FAVOURITES_COUNT = 6; static const uint8_t FAVOURITE_PREFIX_LEN = 6; - uint8_t favourite_contacts[FAVOURITES_COUNT][FAVOURITE_PREFIX_LEN]; + uint8_t favourite_contacts[FAVOURITES_COUNT][FAVOURITE_PREFIX_LEN]; // [del→onContactRemoved] // GPS trail cadence. Logging on/off is a runtime state (Tools › Trail), // not a persisted preference. @@ -229,8 +229,8 @@ struct NodePrefs { // persisted to file // Configured from the Map (Trail screen) "Live share" menu. uint8_t loc_share_enabled; // 0=off (default), 1=auto-sharing on uint8_t loc_share_target_type; // 0=channel, 1=DM contact - uint8_t loc_share_channel_idx; // target channel index (when target_type==0) - uint8_t loc_share_dm_prefix[6]; // target contact pubkey prefix (when target_type==1) + uint8_t loc_share_channel_idx; // target channel index (when target_type==0) [del→onChannelRemoved] + uint8_t loc_share_dm_prefix[6]; // target contact pubkey prefix (when target_type==1) [del→onContactRemoved] uint8_t loc_share_move_idx; // movement gate level (index into locShareMoveMeters) uint8_t loc_share_interval_idx; // min send interval (index into locShareIntervalSecs) uint8_t loc_share_heartbeat_idx; // stationary heartbeat (index into locShareHeartbeatSecs) @@ -251,7 +251,7 @@ struct NodePrefs { // persisted to file // re-reads the latest [LOC] position each evaluation (keyed by pubkey prefix), // so the geofence follows a moving person ("alert when my friend is near"). uint8_t locator_target_kind; // 0=waypoint (static), 1=live contact - uint8_t locator_key[6]; // contact pubkey prefix when target_kind==1 + uint8_t locator_key[6]; // contact pubkey prefix when target_kind==1 [del→onContactRemoved] // Trail auto-pause — when tracking, automatically freeze the trail (timer + // sampling) after the device has sat still for this long, and resume on the diff --git a/examples/companion_radio/ui-new/UITask.cpp b/examples/companion_radio/ui-new/UITask.cpp index bb5046a1..7ad34643 100644 --- a/examples/companion_radio/ui-new/UITask.cpp +++ b/examples/companion_radio/ui-new/UITask.cpp @@ -2406,6 +2406,12 @@ void UITask::clearTargetIfWaypoint(int32_t lat_1e6, int32_t lon_1e6) { the_mesh.savePrefs(); } +// CONTRACT: every NodePrefs field that keys on a contact pubkey/prefix is +// cleared here, so a removed contact can't leave a dangling reference. If you +// add such a field, add its cleanup below (and mark the field in NodePrefs.h). +// Currently covered: favourite_contacts, locator_key, loc_share_dm_prefix, +// dm_notif[], dm_melody[]. Called for both explicit removal and silent +// auto-eviction (see MyMesh CMD_REMOVE_CONTACT / onContactOverwrite). void UITask::onContactRemoved(const uint8_t* pub_key) { if (!_node_prefs || !pub_key) return; bool changed = false; @@ -2445,6 +2451,10 @@ void UITask::onContactRemoved(const uint8_t* pub_key) { if (changed) the_mesh.savePrefs(); } +// CONTRACT: every NodePrefs field that keys on a channel index is cleared here, +// so a channel re-added at a freed slot can't inherit the old one's settings. +// If you add such a field, add its cleanup below (and mark it in NodePrefs.h). +// Currently covered: bot_channel_idx, loc_share_channel_idx, ch_notif_melody_*. void UITask::onChannelRemoved(uint8_t channel_idx) { if (!_node_prefs) return; bool changed = false;