From adc0e9f04ddb5497fe3f154c935249da645edc3b Mon Sep 17 00:00:00 2001 From: Jean-Pierre Martineau Date: Sun, 12 Jul 2026 18:05:19 -0400 Subject: [PATCH] Clearing a channel drops its unread from the global count Unread is tracked in-memory in ChatService, keyed by conversation. A cleared channel's key stayed in that map with its count intact, so the header badge kept counting messages nobody could read anymore. Route the clear through set_active so the key's unread is popped before the slot is wiped. Co-Authored-By: Claude Sonnet 5 --- meshterm/ui/channels.py | 2 ++ tests/test_channels.py | 15 +++++++++++++++ 2 files changed, 17 insertions(+) diff --git a/meshterm/ui/channels.py b/meshterm/ui/channels.py index 553866f..2e928b3 100644 --- a/meshterm/ui/channels.py +++ b/meshterm/ui/channels.py @@ -679,6 +679,8 @@ async def _clear(ctx: "AppContext", device: Device, slot: ChannelSlot) -> bool: ) if choice != "clear": return False + ctx.chat.set_active(slot.conversation.key) # drop its unread before the slot goes away + ctx.chat.set_active(None) await device.set_channel(slot.idx, "", None) # empty name => the slot reads as unused ctx.ui.note(f"[warn]cleared channel {slot.name}[/warn]") return True diff --git a/tests/test_channels.py b/tests/test_channels.py index f114800..afbebf7 100644 --- a/tests/test_channels.py +++ b/tests/test_channels.py @@ -508,6 +508,21 @@ async def test_channel_rows_carry_stats_unread_and_lanes(ctx: AppContext) -> Non assert label.spans[-1].style == "ok" # …and its newest cell reads as live traffic +async def test_clearing_a_channel_reads_off_its_unread(ctx: AppContext) -> None: + """Clearing a channel drops its unread from the global total, not just the slot.""" + from meshterm.ui.channels import _clear + + device = await ctx.device() + await device.set_channel(0, "Ops", bytes(range(16))) + slot = (await read_channel_slots(device))[0] + ctx.chat._unread[slot.conversation.key] = 3 # as the service would after three arrivals + assert ctx.chat.unread_total() == 3 + + ctx.ui = _ScriptedUi(selects=[], texts=[], dialogs=["clear"]) + assert await _clear(ctx, device, slot) is True + assert ctx.chat.unread_total() == 0 + + async def test_show_key_popup_wraps_values_under_header_labels() -> None: """The key popup is labelled blocks, not a table, and long values wrap whole.""" from types import SimpleNamespace