The Contacts title counts the archived too, abbreviating where it's narrow

"Contacts · 198 known + 123 archived", falling back to "Contacts · 198 + 123"
where the frame's rule can't hold the words. The fallback is a new
Screen.short_title the frame picks through fitted_title, on both the bordered
panel and the PicoCalc title bar.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Jean-Pierre MartineauandClaude Opus 5.5 committed 2026-09-27 23:41:53 -04:00
1 parent 53de3c9fb4
commit 3fccfd7971
4 files changed
+73 -9

No files matched your search

+14 -4
View File
@@ -98,9 +98,10 @@ class ContactsScreen(ContactListScreen):
span :data:`~meshterm.ui.contactlist.SORT_COLUMNS` so the hash sort is
reachable.
archived: How many contacts are archived off this device. Non-zero adds the
``View archived contacts`` row and shows the count on it, so the tally is
visible without opening the list; zero draws no row, since a screen should
not offer a way into an empty list.
``View archived contacts`` row and shows the count on it and in the title
(``Contacts · 198 known + 123 archived``), so the tally is visible without
opening the list; zero draws no row and leaves the title at ``… known``,
since a screen should not offer a way into an empty list.
locked: The full keys (lowercase hex) of the contacts locked against archiving;
each of those rows closes its name lane on a padlock.
"""
@@ -132,13 +133,22 @@ class ContactsScreen(ContactListScreen):
label = marked_label("📂", "View archived contacts", "", lane=lane)
label.append(f" · {archived}", style="muted")
tail.append(Choice(title=label, value=_ARCHIVED))
# The title counts both halves of what this device's contacts amount to: those on it
# and those a sweep moved off it. Where the frame is too narrow for the words, the
# figures stay and the words go (``Contacts · 198 + 123``).
known = len(contacts)
title = f"Contacts · {known} known"
if archived:
title += f" + {archived} archived"
super().__init__(
f"Contacts · {len(contacts)} known",
title,
rows=rows,
prefix_bytes=prefix_bytes,
sort=sort,
tail=tail,
)
if archived:
self.short_title = f"Contacts · {known} + {archived}"
def _contact_rows_for(self, locked: frozenset[str]) -> list[ContactRow]:
"""Our own node, then every contact, each marked locked or not by ``locked``."""
+27 -5
View File
@@ -220,6 +220,23 @@ _ESC_ATOM = re.compile(r"(?:^|·\s*)(Esc\s+\S+)\s*$")
_MIN_RULE = 2
def fitted_title(screen: Screen, room: int) -> str:
"""The heading to draw in ``room`` cells: the full title, or its short form where it won't fit.
A title that states figures (``Contacts · 198 known + 123 archived``) would otherwise be
cut by the rule it sits in, and the cut takes the end — the last figure. A screen that
declares a :attr:`~meshterm.ui.tui.screen.Screen.short_title` gets that instead, the same
atoms said tighter; one with none keeps its title and the frame's usual clipping.
Args:
screen: The screen whose heading is drawn.
room: Cells the heading may take, not counting the space either side of it.
"""
if screen.short_title and cell_len(screen.title) > room:
return screen.short_title
return screen.title
def _esc_hint(hint: str) -> str:
"""The ``Esc …`` atom a screen's footer hint ends on, or ``""`` where it has none."""
found = _ESC_ATOM.search(hint or "")
@@ -264,7 +281,10 @@ def _title_bar(screen: Screen, cols: int, more_above: bool, more_below: bool) ->
"""
border = "accent"
head_span = 3 # the arrow pair, plus the space parting it from the rule
label_w = cell_len(screen.title) + 2 if screen.title else 0 # a space either side
# The short form is chosen against the bar with no Esc tail at all: the title is what
# the reader came for, so the tail gives way to it before it gives way to the tail.
title = fitted_title(screen, cols - head_span - 2 - 2 * _MIN_RULE)
label_w = cell_len(title) + 2 if title else 0 # a space either side
atom = _esc_hint(screen.footer_hint)
for esc in (atom, "Esc" if atom else "", ""):
tail_span = cell_len(esc) + 1 if esc else 0 # the space in front of the tail
@@ -276,12 +296,12 @@ def _title_bar(screen: Screen, cols: int, more_above: bool, more_below: bool) ->
bar.append("↑", style=border if more_above else "muted")
bar.append("↓", style=border if more_below else "muted")
bar.append(" ")
if screen.title:
if title:
left = max(1, (rule_span - label_w) // 2)
right = max(1, rule_span - label_w - left)
bar.append("─" * left, style=border)
bar.append(" ", style=None)
bar.append(screen.title, style=title_style(border))
bar.append(title, style=title_style(border))
bar.append(" ", style=None)
bar.append("─" * right, style=border)
else:
@@ -347,11 +367,13 @@ def compose_base(
# a keystroke that need not change a single body line, and a memo without it would
# keep serving the rule the reader has already grown out of.
caption = base.bottom_caption
key = (cols, rows, base.title, caption, footer_hint, more_above, more_below, *visible)
# Rich sets the padded title into the rule's inner ``cols - 4`` cells.
title = fitted_title(base, cols - 4 - 2)
key = (cols, rows, title, caption, footer_hint, more_above, more_below, *visible)
if footer_lane is None and _BASE_BOX_CACHE is not None and _BASE_BOX_CACHE[0] == key:
body = _BASE_BOX_CACHE[1]
else:
panel = _panel_box(base.title, visible, more_above, more_below, "accent", caption)
panel = _panel_box(title, visible, more_above, more_below, "accent", caption)
body = render_lines(Group(panel, footer_row()), cols)
if footer_lane is None:
_BASE_BOX_CACHE = (key, body)
+6
View File
@@ -60,6 +60,11 @@ class Screen:
Attributes:
title: Heading shown at the top of the screen/dialog.
short_title: The abbreviated heading the frame draws instead of :attr:`title` where
the full one would not fit the rule it sits in (see
:func:`~meshterm.ui.tui.frame.fitted_title`) — the same atoms said tighter, so a
narrow frame still carries every figure rather than a title cut off mid-word.
Empty (the default) means the title has no shorter form.
footer_hint: One-line key hint shown in the footer.
scroll: Current vertical scroll offset into the body's rendered lines.
future: Resolved with the screen's result (or :data:`CANCEL`) when it commits.
@@ -104,6 +109,7 @@ class Screen:
"""
title: str = ""
short_title: str = ""
footer_hint: str = "Esc back"
bottom_caption: str = ""
floating: bool = True
+26
View File
@@ -1074,6 +1074,32 @@ def test_the_archived_row_appears_only_when_something_is_archived() -> None:
assert _ARCHIVE not in values and values[-1] == _ARCHIVED
def test_contacts_title_counts_the_archived_and_abbreviates_where_narrow() -> None:
"""The title carries both tallies, and drops the words before it would drop a figure.
Nothing archived keeps the plain ``… known`` title with no short form; with some, the
full ``198 known + 123 archived`` draws wherever it fits and ``198 + 123`` where the
rule is too narrow — on both frames, the bordered panel and the one-row title bar.
"""
from meshterm.core.models import Contact
from meshterm.ui.contacts_screen import ContactsScreen
from meshterm.ui.tui.frame import _title_bar, fitted_title
contacts = [Contact(name=f"n{i}", public_key=f"{i:02x}" * 32) for i in range(198)]
plain = ContactsScreen("Us", "cc" * 32, contacts, 1, {}, _contacts_sort())
assert plain.title == "Contacts · 198 known" and not plain.short_title
both = ContactsScreen("Us", "cc" * 32, contacts, 1, {}, _contacts_sort(), archived=123)
assert both.title == "Contacts · 198 known + 123 archived"
assert both.short_title == "Contacts · 198 + 123"
assert fitted_title(both, 72) == both.title
assert fitted_title(both, 30) == both.short_title
assert "198 known + 123 archived" in _title_bar(both, 53, False, False).plain
assert "Contacts · 198 + 123 " in _title_bar(both, 40, False, False).plain
async def test_archiving_one_contact_takes_it_off_the_device_and_keeps_it() -> None:
"""The single-contact archive: an amber confirm, off the radio, stamped in the store.