In a chat, a blank line comes after the messages of each author
The transcript asked for a gap between sender groups, but an empty Text renders as no line at all, so the gap never showed. A blank line now closes each author's messages, also before a new day's divider, where the platform has 20 rows or more. The 14 rows of the Cardputer Zero keep the transcript dense. A cached slice that starts with the gap pins its day divider one line down. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
a19eadd81b
commit
03b3c0926b
3 files changed
+69
-7
No files matched your search
@@ -18,6 +18,9 @@ can change how things behave. It does not only add new things.
|
||||
message has the same mark, so a message that names you is easy to see. On the PicoCalc,
|
||||
the console cannot draw a light fill, so there the `★` alone marks your messages. Before,
|
||||
your messages showed only the `★`.
|
||||
- **In a chat, a blank line comes after the messages of each author.** Thus the
|
||||
conversation is easier to follow when many people write. The Cardputer Zero has no
|
||||
blank line there, because its screen has only 14 rows and each row shows a message.
|
||||
|
||||
## [0.11.1] - 2026-10-13
|
||||
|
||||
|
||||
+25
-7
@@ -75,11 +75,26 @@ _URL_GUTTER = 2
|
||||
_URL_CODES = False
|
||||
|
||||
|
||||
#: Whether a blank line closes the messages of each author. It costs one row for each
|
||||
#: group. The PicoCalc (26 rows) and a terminal (24) have the rows for it. The 14 rows of
|
||||
#: the Cardputer Zero do not: there a blank line on each group is a message less on the
|
||||
#: screen (JP, 2026-10-13).
|
||||
_GROUP_GAP = True
|
||||
|
||||
#: The fewest rows (``Platform.readable_rows``) for which a transcript has that blank line.
|
||||
_GROUP_GAP_MIN_ROWS = 20
|
||||
|
||||
|
||||
@on_platform
|
||||
def _bind(platform: Platform) -> None:
|
||||
"""Hang codes under URLs where the platform draws them (now and at each switch)."""
|
||||
global _URL_CODES
|
||||
"""Bind the transcript to the platform (now and at each switch).
|
||||
|
||||
The platform says whether codes hang under URLs, and whether it has the rows for a
|
||||
blank line after each author.
|
||||
"""
|
||||
global _URL_CODES, _GROUP_GAP
|
||||
_URL_CODES = platform.url_codes
|
||||
_GROUP_GAP = platform.readable_rows >= _GROUP_GAP_MIN_ROWS
|
||||
|
||||
|
||||
#: The maximum width of a footer hint, in cells (refer to the UX standards).
|
||||
@@ -613,16 +628,21 @@ class ChatScreen(Screen):
|
||||
and cached_ids[idx] == id(message)
|
||||
and cached_acked[idx] == message.acked
|
||||
)
|
||||
# A blank line closes the messages of an author, where the platform has the rows
|
||||
# for it (:data:`_GROUP_GAP`). It is the first line of the slice of the message
|
||||
# that starts the next group, so a cached slice brings it back with it.
|
||||
gap = _GROUP_GAP and bool(lines) and (new_day or group != prev_group)
|
||||
if reusable:
|
||||
start = cached_boundaries[idx - 1] if idx > 0 else 0
|
||||
slice_lines = cached_lines[start : cached_boundaries[idx]]
|
||||
if new_day:
|
||||
self._sticky_headers.append((len(lines), [slice_lines[0]]))
|
||||
at = int(gap) # the divider comes after the gap
|
||||
self._sticky_headers.append((len(lines) + at, [slice_lines[at]]))
|
||||
lines += slice_lines
|
||||
else:
|
||||
if gap:
|
||||
lines.append("")
|
||||
if new_day:
|
||||
if lines:
|
||||
lines += render_lines(Text(""), width)
|
||||
# A day divider is the section heading of this transcript: it pins to the
|
||||
# top row while its day scrolls under it, and ^PgUp/^PgDn step by it.
|
||||
# Thus it has the heading style of the app (``── Label ──`` in the bold
|
||||
@@ -634,8 +654,6 @@ class ChatScreen(Screen):
|
||||
self._sticky_headers.append((len(lines), [divider[0]]))
|
||||
lines += divider
|
||||
if new_day or group != prev_group:
|
||||
if not new_day and lines:
|
||||
lines += render_lines(Text(""), width) # gap between sender groups
|
||||
header = self._group_header(sender, message)
|
||||
lines += render_lines(header, width)
|
||||
if selected:
|
||||
|
||||
@@ -1545,6 +1545,47 @@ def test_channel_own_messages_do_not_merge_with_remote_namesake() -> None:
|
||||
assert rendered.count("you") == 1 and rendered.count("★ Me") == 1
|
||||
|
||||
|
||||
def test_a_blank_line_closes_each_author_where_the_rows_allow() -> None:
|
||||
"""A terminal and the PicoCalc put a blank line after the messages of each author.
|
||||
|
||||
The blank line also comes before the divider of a new day, and never before the first
|
||||
line. The 14 rows of the Cardputer Zero have no row to spare for it. A second paint
|
||||
reads the cached lines, and the divider of the second day still pins at its own line,
|
||||
not at the blank line before it.
|
||||
"""
|
||||
from datetime import datetime, timedelta, timezone
|
||||
|
||||
from meshterm.platforms import CARDPUTER_ZERO, REGULAR, set_platform
|
||||
|
||||
base = datetime(2026, 7, 5, 14, 24, tzinfo=timezone.utc)
|
||||
|
||||
def at(minutes: int) -> datetime:
|
||||
return base + timedelta(minutes=minutes)
|
||||
|
||||
def transcript() -> list[ChatMessage]:
|
||||
return [
|
||||
ChatMessage(text="Alice: hi", is_channel=True, channel_idx=0, created_at=at(0)),
|
||||
ChatMessage(text="Alice: again", is_channel=True, channel_idx=0, created_at=at(1)),
|
||||
ChatMessage(text="Bob: yo", is_channel=True, channel_idx=0, created_at=at(2)),
|
||||
ChatMessage(text="Bob: later", is_channel=True, channel_idx=0, created_at=at(1440)),
|
||||
]
|
||||
|
||||
set_platform(REGULAR)
|
||||
screen = _channel_screen(transcript())
|
||||
lines = [_strip_ansi(line) for line in screen._render_grouped(53)]
|
||||
assert lines[0].startswith("──") and lines[1] == "Alice"
|
||||
assert lines[4] == "" and lines[5] == "Bob" # after the two messages of Alice
|
||||
assert lines[7] == "" and lines[8].startswith("── Mon") # after Bob, before the new day
|
||||
assert lines.count("") == 2
|
||||
first = list(screen._sticky_headers)
|
||||
screen._render_grouped(53) # the cached lines
|
||||
assert screen._sticky_headers == first and [at for at, _ in first] == [0, 8]
|
||||
|
||||
set_platform(CARDPUTER_ZERO)
|
||||
screen = _channel_screen(transcript())
|
||||
assert "" not in [_strip_ansi(line) for line in screen._render_grouped(53)]
|
||||
|
||||
|
||||
def test_a_mention_of_our_name_takes_our_chip() -> None:
|
||||
"""A message that names us is easy to see. A mention of each other node is as before."""
|
||||
screen = _channel_screen([], self_name="Me", self_key=_OUR_KEY)
|
||||
|
||||
Reference in new issue
Block a user