Three interrelated improvements to the chat/channel experience:
Outgoing byte budget & unified transcript (ui/chat.py, ui/tui/prompt.py):
- Cap outgoing messages at MeshCore's UTF-8 limits (150B direct, 130B
channel) and block over-limit sends, keeping the buffer intact to trim.
- Show a live `used/limit` gauge in the compose bar whose used count steps
green→yellow→orange→red by bytes remaining, with truer yellow/orange hues.
- Color the overflow characters red in the compose line (multibyte-safe).
- Render direct chats with the same grouped, per-sender layout as channels
(headers, day dividers) instead of one line per message.
Direct-message delivery retry (services/chat_service.py, persistence,
core/models.py, tools/chat.py, ui/tui/session.py):
- Track outbound acked state (None awaiting / True / False) with a persisted
row_id, add resend_direct + update_chat_ack, and wire Ctrl-R to retry the
latest unacknowledged message in place with ⏳/✅/❌ delivery glyphs.
Channel slot capacity discovery (core/channels.py, core/connection.py,
ui/channels.py, tools):
- Discover the firmware's real channel-slot count via a bounded read-only
probe (Device.channel_capacity) instead of assuming 8; show used/total in
the manager and refresh the chat slot→identity cache after any mutation.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Store channel messages under a slot-independent channel identity (sha256 of
the effective secret) instead of the raw slot index, so reordering or re-keying
slots no longer scrambles history. DB schema v3->v4 adds a channel_id column
with an idempotent migration and a lazy backfill that maps legacy rows to the
channel currently in each slot.
Recognize the firmware's fixed-key default "Public" channel as public: add
DEFAULT_PUBLIC_SECRET / KNOWN_PUBLIC_SECRETS and split is_name_derived (key
reproducible from the name, so it can be re-derived on write) from
is_public_channel (shared meshwide). The default channel no longer mislabels
itself as private, and its well-known key is preserved on edit/reorder.
Lead each chat picker row with a glyph: the channel's openness marker
(# name-derived, globe fixed-key public, lock private) or, for a direct chat,
a speech balloon once we've exchanged messages and a person before. A shared
channel_glyph() keeps the channel manager and the picker in sync.
Round out the messaging UX: live callable row titles and plain-text filtering
in the TUI select screen, plus assorted device-picker, monitor, route-map, and
config-editor menu refinements.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Introduce channel and direct messaging: an always-on ChatService that
records inbound messages to history, a live full-screen chat screen, and
a chat tool (interactive picker plus send/history/list/listen CLI). This
bumps the DB schema to v3 and adds the supporting models, repository
queries, and connection wiring.
Also polish the monitor/menu UX:
- Stop console log handlers from corrupting the full-screen TUI by
detaching the RichHandler for the life of the session (file logging
continues); most visible when toggling the monitor.
- Colour only the header's monitor glyph (green ● on, red ○ off).
- Crop the header to a single line instead of wrapping on narrow terminals.
- Drop the redundant toggle-confirmation window so toggling returns
straight to the main menu.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>