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.
- Replace global _last_reply float with _last_reply_per_sender dict.
A reply to one node no longer blocks all other senders for 5 s.
LRU eviction keeps the dict bounded at 200 entries.
- _get_active_channels() now falls back to BotConfig defaults when
the stored channel set is empty (user never saved a selection).
Bot was silently deaf on first run despite the panel showing all
channels pre-checked.
Closes: bot only replies to first sender in multi-node #test session.
_abbrev_table used a list comprehension inline inside a generator
expression filter. In Python 3, list comprehensions have their own
scope, so the loop variable 'cu' was not visible to the outer 'if'
condition — causing a NameError on every !h / !help DM command.
Extract the comprehension to a local variable 'cats_upper' so both
the iteration and the filter operate on the same pre-built list.