The chat list's columns say LAST and MESSAGE; the live feed keeps TIME
The conversation list now reads CONVERSATION, NEW, LAST, MESSAGE: LAST over the last message's age, MESSAGE over the message, shortening to MSG on a narrow terminal. The live feed's timestamp lane is TIME again, and the channel list keeps NEW. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
fedd5f9608
commit
149eb0cd90
6 files changed
+20
-19
No files matched your search
@@ -767,22 +767,23 @@ def _picker_header(lanes: _Lanes, width: int) -> str:
|
||||
|
||||
The indent covers the select screen's pointer column (2 cells, drawn on choice rows but
|
||||
not separators) plus the marker lane, so each header lands exactly over its column.
|
||||
``NEW`` counts the unread messages and ``WHEN`` is the last message's age — short words,
|
||||
because the lanes under them are a badge and an age, and a heading longer than its lane
|
||||
runs into the next one's. ``WHEN`` still borrows its lane's trailing gap (the age lane is
|
||||
three cells), which leaves a space before the preview.
|
||||
``NEW`` counts the unread messages, ``LAST`` is the last message's age, and ``MESSAGE``
|
||||
is that message — short words, because the lanes under the first two are a badge and an
|
||||
age, and a heading longer than its lane runs into the next one's. ``LAST`` still borrows
|
||||
its lane's trailing gap (the age lane is three cells), which leaves a space before the
|
||||
preview.
|
||||
|
||||
Resolved against the render width (the header row is pinned, so it must stay one row):
|
||||
on a terminal too narrow for the whole line, ``LAST MESSAGE`` gives its cells back a
|
||||
word at a time rather than the line wrapping or losing the label (see
|
||||
on a terminal too narrow for the whole line, ``MESSAGE`` gives its cells back as ``MSG``
|
||||
rather than the line wrapping or losing the label (see
|
||||
:func:`~meshterm.ui.menus.column_header`).
|
||||
"""
|
||||
return column_header(
|
||||
[
|
||||
Lane("CONVERSATION", lanes.label + 2),
|
||||
Lane("NEW", _BADGE_WIDTH + 2),
|
||||
Lane("WHEN", _AGE_WIDTH + 2),
|
||||
Lane(("LAST MESSAGE", "LAST MSG", "LAST")),
|
||||
Lane("LAST", _AGE_WIDTH + 2),
|
||||
Lane(("MESSAGE", "MSG")),
|
||||
],
|
||||
width,
|
||||
indent=2 + lanes.marker,
|
||||
|
||||
@@ -588,7 +588,7 @@ class LiveFeedScreen(Screen):
|
||||
|
||||
Laid out lane for lane against :meth:`_feed_row`, the pointer column included, so
|
||||
every label sits over the values it names. Nothing here sorts — the feed is a
|
||||
stream, and its one order (newest first) is what the ``WHEN`` lane reads down — so
|
||||
stream, and its one order (newest first) is what the ``TIME`` lane reads down — so
|
||||
no column carries the contact list's sort triangle or its lit active-column style;
|
||||
these are signposts, not controls. On a terminal too narrow for the class label
|
||||
the header drops its ``CLASS`` too, leaving the icon lane unlabelled rather than
|
||||
@@ -600,7 +600,7 @@ class LiveFeedScreen(Screen):
|
||||
down the whole column.
|
||||
"""
|
||||
header = Text(" ", style="muted") # the pointer lane
|
||||
header.append(fit_cells("WHEN", _TIME_LANE))
|
||||
header.append(fit_cells("TIME", _TIME_LANE))
|
||||
header.append(fit_cells("CLASS", _ICON_LANE + _FEED_CLASS_WIDTH + _LANE_GAP))
|
||||
header.append(fit_cells("SUBJECT", lanes.subject + _LANE_GAP))
|
||||
header.append(fit_cells("SCOPE", _SCOPE_LANE))
|
||||
|
||||
@@ -364,7 +364,7 @@ class Lane:
|
||||
terminal. Only a lane the line actually overruns is ever shortened.
|
||||
width: Cells the label is padded to — the lane's own width *plus* the gap before
|
||||
the next lane, so a label wider than its column absorbs that gap instead of
|
||||
shifting every lane after it right (``WHEN`` over the chat picker's 3-cell ages).
|
||||
shifting every lane after it right (``LAST`` over the chat picker's 3-cell ages).
|
||||
Zero (the default) for a trailing lane, which just runs to the edge.
|
||||
"""
|
||||
|
||||
|
||||
+3
-3
@@ -1806,14 +1806,14 @@ async def test_picker_pins_its_column_header_over_the_group_heading(
|
||||
screen.handle("down")
|
||||
visible, above, _below = frame._visible_slice(screen, screen.render_body(72), 8)
|
||||
top = [ansi.sub("", row).strip() for row in visible[:2]]
|
||||
assert top[0].startswith("CONVERSATION") and top[0].endswith("LAST MESSAGE")
|
||||
assert top[0].split()[1:3] == ["NEW", "WHEN"] # short words over a badge and an age
|
||||
# Short words over a badge and an age, then the message itself.
|
||||
assert top[0].split() == ["CONVERSATION", "NEW", "LAST", "MESSAGE"]
|
||||
assert top[1] == "── 👤 Direct ──"
|
||||
assert above is True
|
||||
# Too narrow for the whole line, the trailing label shortens — never wraps. The
|
||||
# lane is sized to these six-cell names now, so the squeeze starts well below any
|
||||
# real terminal: the fallback is what is under test, not the width it happens at.
|
||||
assert header.text(40).endswith("LAST MSG")
|
||||
assert header.text(36).endswith(" MSG")
|
||||
assert "\n" not in header.text(30)
|
||||
|
||||
|
||||
|
||||
@@ -263,14 +263,14 @@ def test_livefeed_column_header_sits_over_the_lanes_it_names() -> None:
|
||||
"""The header names each lane, and every label lands on the values beneath it."""
|
||||
screen = _screen(seed=[_obs(node="3d63", kind="telemetry", snr=12.8, rssi=-105.0)])
|
||||
header, row = _stripped(screen.render_body(90))[:2]
|
||||
assert header.split() == ["WHEN", "CLASS", "SUBJECT", "SCOPE", "SNR", "RSSI"]
|
||||
assert header.split() == ["TIME", "CLASS", "SUBJECT", "SCOPE", "SNR", "RSSI"]
|
||||
# Measured in display cells, not characters — the class icon is one character wide
|
||||
# but two cells, so a character index would report every later lane one column early.
|
||||
# The clock is read off the row itself: a literal needle would only match during the
|
||||
# minute it was written in.
|
||||
stamp = re.search(r"\d\d:\d\d:\d\d", row)
|
||||
assert stamp is not None
|
||||
assert _col(header, "WHEN") == _col(row, stamp.group(0))
|
||||
assert _col(header, "TIME") == _col(row, stamp.group(0))
|
||||
assert _col(header, "CLASS") == _col(row, "telemetry") - _ICON_LANE
|
||||
assert _col(header, "SUBJECT") == _col(row, "Hub")
|
||||
# Each reading's label starts where its lane does, like every other label.
|
||||
@@ -285,7 +285,7 @@ def test_livefeed_column_header_is_pinned_and_carries_no_sort_cue() -> None:
|
||||
screen = _screen(seed=[_obs(node=f"n{i}", age_s=i) for i in range(40)])
|
||||
screen.note_viewport(12)
|
||||
lines = _stripped(screen.render_body(100))
|
||||
assert "WHEN" in lines[0]
|
||||
assert "TIME" in lines[0]
|
||||
assert not any(mark in lines[0] for mark in ("▲", "▼")) # nothing here sorts
|
||||
screen.handle("end") # the list's last row — Back
|
||||
screen.handle("up") # …and one up from it, the oldest packet: the window scrolls
|
||||
@@ -475,7 +475,7 @@ def test_livefeed_windows_inside_the_fixed_screen() -> None:
|
||||
lines = screen.render_body(100)
|
||||
assert len(lines) <= 24 # column header + feed window == the viewport, never more
|
||||
body = _plain(lines)
|
||||
assert "WHEN" in body # the pinned column header is still there
|
||||
assert "TIME" in body # the pinned column header is still there
|
||||
assert "↓" in body and "more" in body # hidden feed rows are counted below
|
||||
|
||||
|
||||
|
||||
@@ -218,7 +218,7 @@ def test_the_picocalc_feed_names_the_class_without_an_icon() -> None:
|
||||
try:
|
||||
screen = _feed(_scoped("harbour"))
|
||||
header, row = _stripped(screen.render_body(53))[:2]
|
||||
assert header.split()[:2] == ["WHEN", "CLASS"]
|
||||
assert header.split()[:2] == ["TIME", "CLASS"]
|
||||
assert _col(header, "CLASS") == _col(row, "chan text")
|
||||
finally:
|
||||
set_platform(REGULAR)
|
||||
|
||||
Reference in new issue
Block a user