WHAT
- route_page: _default_reply_channel() derives the reply select default
from msg.channel instead of the first entry in the channel list
- models: new Message.date field, now_date() and display_timestamp();
format_line() and incoming()/outgoing() use them
- message_archive: persist "date" alongside "time" and "timestamp_utc"
- room_server_panel + route_page info header use display_timestamp()
- VERSION 1.22.2 -> 1.22.3
WHY
The route page exists to show one specific message, so that message —
not the ordering of the channel list — is the correct source for the
reply default. The date could not be fixed at the render site because
Message had no date at all: now_timestamp() yields HH:MM:SS and the
date lived only as timestamp_utc inside the archive layer, unread by
from_dict(). A first-class field plus a single formatting helper fixes
the cause and keeps the format in one place.
NOTES
No breaking changes. The archive schema gains one additive field;
pre-1.22.3 entries have their date derived from timestamp_utc
(converted to local time) and fall back to time-only when absent.
Message.incoming() gained an optional keyword-only date parameter.
Message display lines are ~11 characters wider.
fix(packet_decoder): brute-force channel resolution when hash lookup fails
ChannelCrypto.calculate_channel_hash() and the MeshCore firmware compute
different channel identifiers for the same secret, causing _hash_to_idx to
return None for all hashtag-channel messages. Fallback: try each registered
key individually via a single-key keystore. First valid decryption wins.
Result cached in _hash_to_idx for O(1) resolution on subsequent packets.
fix(events): channel-agnostic sentinel prevents cross-channel duplicates
on_rx_log now marks '*' in DualDeduplicator after storing a message.
on_channel_msg checks this sentinel and suppresses storage regardless of
channel_idx or message_hash differences between the two library systems.
Fixes #mc-radar messages appearing in #weather and vice versa.
fix(events): secondary path-cache keyed by content for hash-mismatch
_path_cache keyed by meshcoredecoder hash; CHANNEL_MSG_RECV carries meshcore
hash (different value). Added _path_cache_by_content ("sender:text[:100]")
as fallback so path_hashes are recovered when the two hashes disagree.
fix(commands,cache): remove stale channel key after del_channel reindex
Cache entry for old_idx not removed after slot move, causing same channel
to appear twice. New DeviceCache.remove_channel_key(idx) called after each
move. asyncio.sleep(0.5) added before re-discovery to let device settle.
feat(channel_panel,commands,dashboard): channel edit — move/reindex support
First channel-edit capability in the GUI. New '↕️ Move / Reindex' mode in
Channel Manager dialog. Source channel selected from dropdown, target index
in number field. ↕ button inline with 🗑 in Messages and Archive submenus.
_cmd_move_channel reads secret from cache or device, writes new slot, clears
old slot, updates cache atomically, triggers re-discovery with settle delay.