feat(bot): room-server target, tab-carousel UI, per-target independence

Auto-Reply Bot gains a third target (room servers, alongside DM and
channel), new {name}/{hops} reply placeholders, and a DM all/favourites
allow-list. BotScreen is redesigned as a circular tab carousel (Direct /
Channel / Room / Other, same interaction as Nearby Nodes' filter tabs)
instead of one long scrolling list, which also exposed and fixed a
leftover coupling where Channel/Room trigger-replies and their !command
handling secretly depended on the DM tab's Enable/Commands toggles —
each target's Enable and Commands are now fully independent.

- NodePrefs: bot_room_enabled/prefix/trigger/reply, bot_dm_scope,
  bot_commands_ch/bot_commands_room (bot_commands_enabled repurposed as
  DM-only). SCHEMA_SENTINEL 0xC0DE001D -> 0xC0DE001F; sizeof unchanged
  at 2712 (new bytes absorbed existing padding, verified via a
  standalone host compile + offsetof check).
- DataStore: persists all new fields; seeds bot_commands_ch/room from
  the old shared bot_commands_enabled on upgrade so existing
  channel/room command behaviour isn't silently lost.
- MyMeshBot: tryBotReplyRoom/tryBotRoomCommand mirror the channel bot's
  shape but post via sendMessage (room relays to members itself);
  requires an existing login session with that room, same as a manual
  post would. botDmSenderAllowed() gates DM trigger-reply/commands on
  the favourites bit when bot_dm_scope=Fav.
- MsgExpand: {name}/{hops} as optional trailing params (default
  nullptr/-1, no existing caller affected) — deliberately not exposed
  on the general compose keyboard, only on bot Reply fields.
- QuickMsgScreen/UITask: room-target picker (mirrors the channel
  picker), routing through the existing room-login prompt when there's
  no saved password yet.
- BotScreen: tab carousel (LEFT/RIGHT switches tabs, UP/DOWN moves
  rows, Enter is now the only way to change a value); Enable split out
  of Channel/Room's combo row; Quiet Hours gained a stepper sub-mode;
  Commands moved from a shared toggle into each tab.
- docs/tools_screen.md updated for the new tab layout and behaviour.

Not build-verified — no PlatformIO toolchain in this environment.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Jakub
2026-07-10 20:16:59 +02:00
parent dfb993de53
commit afd7c0ee78
11 changed files with 744 additions and 181 deletions

View File

@@ -1688,6 +1688,16 @@ void UITask::pickLocShareTarget() {
setCurrScreen(quick_msg);
}
void UITask::pickBotChannelTarget() {
((QuickMsgScreen*)quick_msg)->startPickBotChannel();
setCurrScreen(quick_msg);
}
void UITask::pickBotRoomTarget() {
((QuickMsgScreen*)quick_msg)->startPickBotRoom();
setCurrScreen(quick_msg);
}
int UITask::getRecentDMContacts(uint8_t out[][NodePrefs::FAVOURITE_PREFIX_LEN], int max) const {
return ((QuickMsgScreen*)quick_msg)->getRecentDMContacts(out, max);
}
@@ -2557,6 +2567,14 @@ void UITask::onContactRemoved(const uint8_t* pub_key) {
_node_prefs->loc_share_enabled = 0;
changed = true;
}
// Same fail-closed rule for the room bot's target: the room contact is
// gone, disable it rather than risk a re-added contact silently inheriting
// the old bot config.
if (_node_prefs->bot_room_enabled
&& memcmp(_node_prefs->bot_room_prefix, pub_key, NodePrefs::FAVOURITE_PREFIX_LEN) == 0) {
_node_prefs->bot_room_enabled = 0;
changed = true;
}
// Per-contact mute/melody overrides — only 16 slots shared across every
// contact, so an orphaned entry isn't just stale, it can eventually starve
// new overrides for contacts that still exist. Keyed by a 4-byte prefix