The archive confirm names the contact with its type icon, as the Contacts list does

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Jean-Pierre MartineauandClaude Opus 5 committed 2026-09-17 00:54:16 -04:00
1 parent 5cba667542
commit 4ba0a29691
3 files changed
+48 -9

No files matched your search

+23 -3
View File
@@ -1538,10 +1538,30 @@ async def _archive_contact(ctx: AppContext, contact: Contact, self_key: str, lab
assert isinstance(ctx.ui, TuiUi) # guaranteed by open_node_detail
session = ctx.ui.session
# The contact is named as the Contacts list names it: the type mark in its own colour,
# then the name in its key-derived hue (a bare id standing in for a nameless one is
# ``node.unknown``). The prose carries the amber span by span rather than as the
# prompt's base style, whose bold Rich would merge into the mark.
glyph, glyph_style = NODE_GLYPHS.get(contact.node_type, DEFAULT_GLYPH)
prompt = Text()
prompt.append("Archive ", style="warn")
prompt.append(f"{glyph} ", style=glyph_style)
prompt.append(
label,
style=(
name_style(label, contact.public_key or contact.key_prefix)
if contact.name
else "node.unknown"
),
)
prompt.append(
"? It comes off this device's contact list, freeing a slot for a new one. MeshTerm "
"keeps it — with its key, reception history and messages — and you can restore it "
"at any time.",
style="warn",
)
if not await ctx.ui.dialog(
f"Archive {label}? It comes off this device's contact list, freeing a slot for a "
"new one. MeshTerm keeps it — with its key, reception history and messages — and "
"you can restore it at any time.",
prompt,
[("Cancel", False), ("Archive", True)],
title="Archive contact",
default=1,
+7 -3
View File
@@ -330,7 +330,7 @@ class Ui:
async def dialog(
self,
prompt: str,
prompt: str | Text,
buttons: list[tuple[str, Any]],
*,
title: str = "",
@@ -348,6 +348,10 @@ class Ui:
sibling. Buttons follow the platform-dialog convention: the safe way out sits on
the left and the committing action on the right, which is also the sensible
``default`` so Enter commits it while Esc always backs out.
A pre-styled :class:`~rich.text.Text` prompt is drawn as it is, so it can name a
node in its own colours; the tier then themes only the border, and the prompt's
prose carries the tier's colour itself.
"""
raise NotImplementedError
@@ -634,7 +638,7 @@ class PlainUi(Ui):
async def dialog(
self,
prompt: str,
prompt: str | Text,
buttons: list[tuple[str, Any]],
*,
title: str = "",
@@ -905,7 +909,7 @@ class TuiUi(Ui):
async def dialog(
self,
prompt: str,
prompt: str | Text,
buttons: list[tuple[str, Any]],
*,
title: str = "",
+18 -3
View File
@@ -1035,12 +1035,15 @@ async def test_archiving_one_contact_takes_it_off_the_device_and_keeps_it() -> N
import logging
from types import SimpleNamespace
from meshterm.core.models import Contact
from meshterm.core.models import NODE_TYPE_REPEATER, Contact
from meshterm.ui.node_detail_screen import _archive_contact
from meshterm.ui.surface import TuiUi
from meshterm.ui.theme import name_style
from meshterm.ui.tui.session import TuiSession
hub = Contact(name="Hub", public_key="3d" * 32, key_prefix="3d" * 6)
hub = Contact(
name="Hub", public_key="3d" * 32, key_prefix="3d" * 6, node_type=NODE_TYPE_REPEATER
)
removed: list[str] = []
archived: list[tuple[str, str]] = []
invalidated: list[bool] = []
@@ -1073,7 +1076,19 @@ async def test_archiving_one_contact_takes_it_off_the_device_and_keeps_it() -> N
assert asked[0]["danger"] is True and "destructive" not in asked[0]
assert [label for label, _v in asked[0]["buttons"]] == ["Cancel", "Archive"]
assert asked[0]["default"] == 1
assert "restore it at any time" in asked[0]["prompt"]
prompt = asked[0]["prompt"]
assert "restore it at any time" in prompt
# The contact is named as the Contacts list names it — its type mark to the left of the
# name, each in its own colour — while the sentence around them keeps the amber.
assert prompt.plain.startswith("Archive ▲ Hub? ")
def styles_at(index: int) -> list[str]:
return [str(span.style) for span in prompt.spans if span.start <= index < span.end]
assert styles_at(prompt.plain.index("▲")) == ["type.repeater"]
assert styles_at(prompt.plain.index("Hub")) == [name_style("Hub", "3d" * 32)]
assert styles_at(0) == styles_at(prompt.plain.index("restore")) == ["warn"]
assert not prompt.style, "a base style would merge the amber's bold into the mark"
# Off the radio, kept here, and the cached contact list dropped so the list re-reads.
assert removed == ["Hub"]
assert archived == [("cc" * 32, "3d" * 32)]