In a chat, the ends of our chip mark a message that names us
The star at the left of the time becomes two cells in the fill of our chip: the notch, then the point, with no label between them. Thus the mark has the colour of the mention chip in the body. Without chips, it is ">>" in our hue. The Messages list puts the same mark at the left of the age. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
427bee836b
commit
68d5d03324
9 files changed
+123
-48
No files matched your search
+4
-4
@@ -15,10 +15,10 @@ can change how things behave. It does not only add new things.
|
||||
your messages under `★` and your node's name, and the Messages list shows them in the
|
||||
same way. Your name has the colour of your key, but in reverse: dark text on a light
|
||||
fill, where each other sender has light text on a dark fill. A mention of your name has
|
||||
the same reversed colours, and the message that has it gets a `★` at the left of its
|
||||
time. Thus the messages that name you are easy to find down the left side of the chat.
|
||||
On the PicoCalc, the console cannot draw a light fill, so there the `★` alone marks you.
|
||||
Before, your messages showed only the `★`.
|
||||
the same reversed colours, and the message that has it gets a small arrow mark in your
|
||||
colour at the left of its time. Thus the messages that name you are easy to find down
|
||||
the left side of the chat. On the PicoCalc, the console cannot draw a light fill, so
|
||||
there the `★` marks your messages. Before, your messages showed only the `★`.
|
||||
- **A mention has no `@` in front of it.** The coloured box around the name already says
|
||||
that it is a mention. On a terminal that cannot draw the boxes, a mention keeps its `@`.
|
||||
- **The line where you write a message shows each mention as a box.** When you type the
|
||||
|
||||
@@ -488,9 +488,12 @@ as a grouped list does. Filling a page in is editing its `.md`; no Python follow
|
||||
the star in the chip's dark ink: a yellow star measured under 3:1 on the light fill for
|
||||
238 of 256 hues, and the star on the page right of the chip was tried and put back (JP,
|
||||
2026-10-13). A **mention** of us is the turned-over chip with no star
|
||||
(`mention_chip(you=True)`); the message carrying it gets the `★` in the two cells left
|
||||
of its time instead (`❯ ★ 12:34 body` — every time sits two cells right to keep the
|
||||
column; the Messages list puts it left of the row's age). Arrow mode draws the chip as
|
||||
(`mention_chip(you=True)`); the message carrying it gets **the two ends of our chip**
|
||||
in the two cells left of its time instead — notch then point, no label, in our chip's
|
||||
fill (`widgets.mention_mark` → `pathline.chip_ends`; `>>` in our hue in arrow mode;
|
||||
it replaced a `★`, JP 2026-10-13). Every time sits two cells right to keep the column
|
||||
(`❯`, a space, the mark, then `12:34 body`), and the Messages list puts the mark left
|
||||
of the row's age. Arrow mode draws the chip as
|
||||
a padded reverse-video block. The PicoCalc can't put a bright slot behind text, so
|
||||
there the chip keeps the normal colours and the `★` alone marks us. With no name or key from the device, the bare `★` chip stays. Path lines
|
||||
(delivery paths, the packet viewer, routes) keep `★`.
|
||||
|
||||
@@ -57,7 +57,6 @@ from ..services.trace_runner import (
|
||||
)
|
||||
from ..ui import renderers
|
||||
from ..ui.fields import ChannelRef, NodeRef
|
||||
from ..ui.marks import SELF_MARK
|
||||
from ..ui.menus import Lane, column_header, fit_cells, section_heading
|
||||
from ..ui.pathline import PathHop
|
||||
from ..ui.termfont import powerline_enabled
|
||||
@@ -71,6 +70,7 @@ from ..ui.widgets import (
|
||||
format_age,
|
||||
hop_chip,
|
||||
mention_chip,
|
||||
mention_mark,
|
||||
self_chip,
|
||||
)
|
||||
from .base import Tool, ToolResult, register
|
||||
@@ -986,15 +986,15 @@ def _title(
|
||||
text.append(" " * _BADGE_WIDTH)
|
||||
# The relative age lane (right-aligned) is between the badge and the message text. Thus
|
||||
# the ages are in one neat column, and all the previews start at the same position. A
|
||||
# last message that names us has the ``★`` at the left of its age, the same as the
|
||||
# transcript has it at the left of the time (refer to ChatScreen._body_lines).
|
||||
# last message that names us has the ends of our chip at the left of its age, the same
|
||||
# as the transcript has them at the left of the time (refer to ChatScreen._body_lines).
|
||||
age = _ago(last.created_at) if last is not None else ""
|
||||
lane = f" {age:>{_AGE_WIDTH}}"
|
||||
if age and last is not None and _names_us(last, us):
|
||||
star = len(lane) - len(age) - 2 # the star, a space, then the age
|
||||
text.append(lane[:star])
|
||||
text.append(SELF_MARK[0], style="type.you")
|
||||
text.append(lane[star + 1 :], style="muted")
|
||||
mark = len(lane) - len(age) - 2 # the two cells of the mark, then the age
|
||||
text.append(lane[:mark])
|
||||
text.append_text(mention_mark(us[1]))
|
||||
text.append(age, style="muted")
|
||||
else:
|
||||
text.append(lane, style="muted")
|
||||
text.append(" ")
|
||||
@@ -1057,8 +1057,8 @@ def _preview_text(
|
||||
|
||||
- Our own messages start with our chip, ``★ Name`` turned over (refer to
|
||||
:func:`~meshterm.ui.widgets.self_chip`). A mention of our name has the chip that is
|
||||
turned over, with no star. The star of a message that names us is at the left of the
|
||||
age of the row (refer to :func:`_title`).
|
||||
turned over, with no star. A message that names us has the ends of our chip at the
|
||||
left of the age of the row (refer to :func:`_title`).
|
||||
- In a received channel message, the inline ``Name:`` sender is the author, in the hue
|
||||
that comes from its key (the node grey when no known node has the name).
|
||||
- A room post starts with its author, named by key in the same way as the room screen
|
||||
|
||||
+22
-14
@@ -33,7 +33,6 @@ from ..core.models import ChatMessage, Contact, Conversation, Message, utcnow
|
||||
from ..core.regions import WILDCARD
|
||||
from ..platforms import Platform, on_platform
|
||||
from .compose import MentionEditor
|
||||
from .marks import SELF_MARK
|
||||
from .pathline import PATH_INK, PathHop
|
||||
from .qr import QrScreen, qr_strip
|
||||
from .theme import snr_style
|
||||
@@ -45,7 +44,15 @@ from .tui.prompt import (
|
||||
from .tui.render import render_hanging, render_lines, right_aligned_tail
|
||||
from .tui.screen import CANCEL, Screen
|
||||
from .tui.spinner import Spinner, spinner_interval
|
||||
from .widgets import DESTINATION, ORIGIN, hop_chip, mention_hop, name_chip, self_chip
|
||||
from .widgets import (
|
||||
DESTINATION,
|
||||
ORIGIN,
|
||||
hop_chip,
|
||||
mention_hop,
|
||||
mention_mark,
|
||||
name_chip,
|
||||
self_chip,
|
||||
)
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from ..context import AppContext
|
||||
@@ -69,7 +76,7 @@ _WORD = re.compile(r"\S+")
|
||||
|
||||
#: The column where a URL starts when it is too long to hang under its body: the column
|
||||
#: after the ``❯`` that is in front of the first line of the selected message. A message
|
||||
#: that names us has its ``★`` in this column, and the timestamp follows it.
|
||||
#: that names us has its mark in this column, and the timestamp follows it.
|
||||
_URL_GUTTER = 2
|
||||
|
||||
#: Whether the URLs of a message are drawn as QR codes under it (``Platform.url_codes``,
|
||||
@@ -768,12 +775,14 @@ class ChatScreen(Screen):
|
||||
is marked as the reply target (the same as the ``❯`` pointer and the highlight of
|
||||
the select screen).
|
||||
|
||||
A message that mentions our name has the ``★`` of the app at the left of its time
|
||||
(JP, 2026-10-13). The chip of the mention in the body is turned over, but it has no
|
||||
star (refer to :func:`~meshterm.ui.widgets.mention_hop`). Thus the star of a message
|
||||
that names us is in one column, where the eye finds it when it goes down the
|
||||
transcript. Each time has the two cells of that column in front of it, so that all
|
||||
the times stay in one column too.
|
||||
A message that mentions our name has a mark at the left of its time: the two ends
|
||||
of our chip, in its colour, with no label (refer to
|
||||
:func:`~meshterm.ui.widgets.mention_mark`, JP, 2026-10-13). The chip of the mention
|
||||
in the body is turned over, but it has no star (refer to
|
||||
:func:`~meshterm.ui.widgets.mention_hop`). Thus the mark of a message that names us
|
||||
is in one column, where the eye finds it when it goes down the transcript. Each time
|
||||
has the two cells of that column in front of it, so that all the times stay in one
|
||||
column too.
|
||||
|
||||
A URL is never cut where the screen can hold it. A URL that is too long for the
|
||||
body lane moves out to the column after the ``❯``, on its own line (refer to
|
||||
@@ -789,8 +798,7 @@ class ChatScreen(Screen):
|
||||
prefix = Text()
|
||||
prefix.append("❯ " if selected else " ", style="cursor" if selected else None)
|
||||
if self._names_us(body):
|
||||
prefix.append(SELF_MARK[0], style="type.you")
|
||||
prefix.append(" ")
|
||||
prefix.append_text(mention_mark(self._self_key))
|
||||
else:
|
||||
prefix.append(" ")
|
||||
prefix.append(f"{stamp:%H:%M} ", style="cursor" if selected else "muted")
|
||||
@@ -847,8 +855,8 @@ class ChatScreen(Screen):
|
||||
Thus the user sees that a mention belongs to the node that it names. The text
|
||||
around the mentions keeps the base style of the line (``cursor`` when the message
|
||||
is the selected reply target, or else no style). A mention of our own name has our
|
||||
chip, turned over, and the line has a ``★`` at the left of its time (refer to
|
||||
:meth:`_body_lines`). Thus a message that names us is easy to see.
|
||||
chip, turned over, and the line has the ends of our chip at the left of its time
|
||||
(refer to :meth:`_body_lines`). Thus a message that names us is easy to see.
|
||||
|
||||
The chip opens on the notch, because the node that a mention names is the
|
||||
destination of a route that came from the sender (refer to :meth:`_group_header`,
|
||||
@@ -881,7 +889,7 @@ class ChatScreen(Screen):
|
||||
return mention_hop(name, self._sender_key(name, mention=True))
|
||||
|
||||
def _names_us(self, body: str) -> bool:
|
||||
"""Whether ``body`` has a mention of our name (it gets a ``★`` before its time)."""
|
||||
"""Whether ``body`` has a mention of our name (it gets a mark before its time)."""
|
||||
if not self._self_name:
|
||||
return False
|
||||
return any(match.group(1) == self._self_name for match in MENTION.finditer(body))
|
||||
|
||||
@@ -1294,6 +1294,27 @@ def _bind_chips(platform: Platform) -> None:
|
||||
_colours_of, _WEIGHT, _FLAT = _slot_colours, "not bold ", "not bold "
|
||||
|
||||
|
||||
def chip_ends(hop: PathHop) -> Text:
|
||||
"""The two ends of the chip of ``hop`` with no label between them: the notch, then the point.
|
||||
|
||||
It is two cells in the fill of the chip, the same two cells that open and close a chip
|
||||
of a destination (refer to :meth:`PathLine._notch`). A chat puts it at the left of the
|
||||
time of a message that names us, in the fill of our chip (refer to
|
||||
:func:`~meshterm.ui.widgets.mention_mark`). The colours come from the palette of the
|
||||
platform, so the PicoCalc draws it in the fill that the chip has there.
|
||||
|
||||
Args:
|
||||
hop: The hop whose chip gives the fill.
|
||||
|
||||
Returns:
|
||||
The two styled cells.
|
||||
"""
|
||||
fill = _colours_of(hop).fill
|
||||
text = PathLine._notch(fill)
|
||||
text.append(POWERLINE_SEP, style=f"{_FLAT}{fill}")
|
||||
return text
|
||||
|
||||
|
||||
def _shorten(value: str, hash_bytes: int | None) -> str:
|
||||
"""Bare hex in lower case, truncated to ``hash_bytes`` bytes (whole when it is empty)."""
|
||||
raw = value.lower().removeprefix("0x")
|
||||
|
||||
@@ -173,10 +173,6 @@ MESH_THEME = Theme(
|
||||
"type.repeater": "#a78bfa",
|
||||
"type.room": "#ffffff",
|
||||
"type.sensor": "#fb923c",
|
||||
# The ``★`` of our node where it stands alone on the page as a mark: after our name
|
||||
# in a chat, and before the time of a message that names us. It is the yellow of
|
||||
# the map (``marks.SELF_MARK``), with a name so that the console chooses its slot.
|
||||
"type.you": "#facc15",
|
||||
# A region name (the scope of a flood). It is not a node, so it has no hue from the
|
||||
# node wheel. It is a light slate. Its slant makes it different from prose, as a
|
||||
# tag is different from a sentence.
|
||||
@@ -416,7 +412,6 @@ MESH_THEME_16 = Theme(
|
||||
"type.repeater": "not bold color(5)",
|
||||
"type.room": "color(15)",
|
||||
"type.sensor": "not bold color(3)",
|
||||
"type.you": "color(11)",
|
||||
# A region name: the VT has no italic, and each chromatic slot is a node hue. Thus
|
||||
# the style is the plain light grey. The word "scope" in front of it makes it
|
||||
# different from other text.
|
||||
|
||||
+26
-3
@@ -57,7 +57,7 @@ from .marks import (
|
||||
LabelRgbOf,
|
||||
parse_hex,
|
||||
)
|
||||
from .pathline import SELF_GLYPH, PathHop, PathLine, path_line
|
||||
from .pathline import SELF_GLYPH, PathHop, PathLine, chip_ends, path_line
|
||||
from .termfont import powerline_enabled
|
||||
from .theme import glyph, mark_rgb, name_style, node_style, snr_style
|
||||
|
||||
@@ -698,8 +698,8 @@ def self_chip(name: str | None, key: str | None, *, role: str | None = None) ->
|
||||
:attr:`~meshterm.ui.pathline.PathHop.inverse`).
|
||||
|
||||
A mention of our name is a different mark (refer to :func:`mention_chip`). The chip is
|
||||
turned over, but it has no star. The star of a message that names us is at the left of
|
||||
the time of the message (refer to :meth:`~meshterm.ui.chat.ChatScreen._body_lines`).
|
||||
turned over, but it has no star. The message that names us has the ends of our chip at
|
||||
the left of its time (refer to :func:`mention_mark`).
|
||||
|
||||
Our node with no name or no key (the device did not report them) keeps the bare ``★``
|
||||
chip of a route.
|
||||
@@ -717,6 +717,29 @@ def self_chip(name: str | None, key: str | None, *, role: str | None = None) ->
|
||||
return hop_chip(PathHop(f"{SELF_GLYPH} {name}", key=key, inverse=True), role=role)
|
||||
|
||||
|
||||
def mention_mark(key: str | None) -> Text:
|
||||
"""The mark of a message that names us: the two ends of our chip, with no label.
|
||||
|
||||
The notch, then the point, in the fill of our chip (refer to
|
||||
:func:`~meshterm.ui.pathline.chip_ends`). The chat puts it in the two cells at the left
|
||||
of the time of each message that mentions our name, and the Messages list puts it at
|
||||
the left of the age of a row whose last message does (JP, 2026-10-13). It replaced a
|
||||
``★``. The mark has the colour of the chip that the mention has in the body, so the eye
|
||||
joins the two. Where the terminal draws no chips, it is ``>>`` in our hue, the colour
|
||||
that our chip has there.
|
||||
|
||||
Args:
|
||||
key: Our public key, which gives the hue. With ``None``, the mark has the keyless
|
||||
grey, the same as the chip of the mention.
|
||||
|
||||
Returns:
|
||||
The mark, two cells wide.
|
||||
"""
|
||||
if powerline_enabled():
|
||||
return chip_ends(PathHop("", key=key or None, inverse=bool(key)))
|
||||
return Text(">>", style=node_style(key) if key else "node.unknown")
|
||||
|
||||
|
||||
def mention_hop(name: str, key: str | None, *, you: bool = False) -> PathHop:
|
||||
"""The hop of a mention: the node that a message names, as its chip shows it.
|
||||
|
||||
|
||||
+14
-10
@@ -1608,7 +1608,7 @@ def test_a_blank_line_closes_each_author_where_the_rows_allow(
|
||||
def test_a_mention_of_our_name_takes_our_chip_with_no_star(powerline) -> None:
|
||||
"""The chip of a mention of us is turned over, as our sender chip, but it has no star.
|
||||
|
||||
The star of a message that names us is at the left of its time (refer to the next
|
||||
The mark of a message that names us is at the left of its time (refer to the next
|
||||
test). With chips, no mention has its ``@``.
|
||||
"""
|
||||
from meshterm.ui.theme import node_style
|
||||
@@ -1621,8 +1621,12 @@ def test_a_mention_of_our_name_takes_our_chip_with_no_star(powerline) -> None:
|
||||
assert str(me.style).endswith(f"on {node_style(_OUR_KEY).split()[-1]}") # a light fill
|
||||
|
||||
|
||||
def test_a_message_that_names_us_has_the_star_left_of_its_time() -> None:
|
||||
"""The ``★`` goes in the two cells before the time, and each time moves two cells right."""
|
||||
def test_a_message_that_names_us_has_the_mark_left_of_its_time() -> None:
|
||||
"""The mark goes in the two cells before the time, and each time moves two cells right.
|
||||
|
||||
The suite draws no chips, so the mark is ``>>`` in our hue (refer to the widget tests
|
||||
for the two ends of the chip).
|
||||
"""
|
||||
from datetime import datetime, timezone
|
||||
|
||||
at = datetime(2026, 7, 5, 14, 24, tzinfo=timezone.utc)
|
||||
@@ -1633,12 +1637,12 @@ def test_a_message_that_names_us_has_the_star_left_of_its_time() -> None:
|
||||
]
|
||||
screen = _channel_screen(messages, self_name="Me", self_key=_OUR_KEY)
|
||||
rows = _strip_ansi("\n".join(screen._render_grouped(72))).splitlines()
|
||||
assert any(row.startswith(f" ★ {stamp} hi") for row in rows)
|
||||
assert any(row.startswith(f" >>{stamp} hi") for row in rows)
|
||||
assert any(row.startswith(f" {stamp} hi") for row in rows) # the other message
|
||||
screen.handle("up")
|
||||
screen.handle("up") # the first message: the pointer, then the star
|
||||
screen.handle("up") # the first message: the pointer, then the mark
|
||||
rows = _strip_ansi("\n".join(screen._render_grouped(72))).splitlines()
|
||||
assert any(row.startswith(f"❯ ★ {stamp} hi") for row in rows)
|
||||
assert any(row.startswith(f"❯ >>{stamp} hi") for row in rows)
|
||||
|
||||
|
||||
def _channel_screen(
|
||||
@@ -1750,7 +1754,7 @@ def test_the_compose_line_keeps_a_chip_whole_when_it_wraps(powerline) -> None:
|
||||
assert sum(chip in line for line in lines) == 1, width
|
||||
|
||||
|
||||
def test_a_row_whose_last_message_names_us_has_the_star_left_of_its_age(
|
||||
def test_a_row_whose_last_message_names_us_has_the_mark_left_of_its_age(
|
||||
repo: Repository,
|
||||
) -> None:
|
||||
"""The Messages list marks the row as the transcript marks the message."""
|
||||
@@ -1760,10 +1764,10 @@ def test_a_row_whose_last_message_names_us_has_the_star_left_of_its_age(
|
||||
named = ChatMessage(text="Bob: hi @[Me]", is_channel=True, channel_id="c0")
|
||||
other = ChatMessage(text="Bob: hi @[Zed]", is_channel=True, channel_id="c0")
|
||||
lanes = _lanes([conv])
|
||||
starred = _title(ctx, conv, {"chan:c0": named}, _NO_KEYS, lanes, us=us).plain
|
||||
marked = _title(ctx, conv, {"chan:c0": named}, _NO_KEYS, lanes, us=us).plain
|
||||
plain = _title(ctx, conv, {"chan:c0": other}, _NO_KEYS, lanes, us=us).plain
|
||||
assert "★ now " in starred and "★" not in plain
|
||||
assert starred.index("now") == plain.index("now") # the star takes a cell of the gap
|
||||
assert ">>now " in marked and ">>" not in plain
|
||||
assert marked.index("now") == plain.index("now") # the mark takes the cells of the gap
|
||||
|
||||
|
||||
def test_channel_typing_clears_selection() -> None:
|
||||
|
||||
@@ -30,6 +30,7 @@ from meshterm.ui.widgets import (
|
||||
hop_chip,
|
||||
mention_chip,
|
||||
mention_hop,
|
||||
mention_mark,
|
||||
name_chip,
|
||||
path_text,
|
||||
revisit_note,
|
||||
@@ -398,6 +399,26 @@ def test_a_mention_of_us_is_turned_over_with_no_star(powerline) -> None:
|
||||
assert not mention_hop("Me", None, you=True).inverse # no key: no hue to turn over
|
||||
|
||||
|
||||
def test_the_mark_of_a_message_that_names_us_is_the_ends_of_our_chip(powerline) -> None:
|
||||
"""The notch, then the point, in the fill of our chip, and no label between them."""
|
||||
powerline(True)
|
||||
mark = mention_mark("5e7f")
|
||||
fill = _span_style(mention_chip("Me", "5e7f", you=True), "Me").bgcolor.name
|
||||
|
||||
assert mark.plain == POWERLINE_SEP * 2
|
||||
notch, point = (Style.parse(str(span.style)) for span in mark.spans)
|
||||
assert notch.reverse and notch.color.name == fill # cut out of the fill
|
||||
assert not point.reverse and point.color.name == fill # the point of the fill
|
||||
|
||||
|
||||
def test_the_mark_without_chips_is_two_arrows_in_our_hue(powerline) -> None:
|
||||
"""With no chips, the mark is ``>>``, in the hue that our chip has in arrow mode."""
|
||||
powerline(False)
|
||||
mark = mention_mark("5e7f")
|
||||
assert mark.plain == ">>" and str(mark.style) == node_style("5e7f")
|
||||
assert str(mention_mark(None).style) == "node.unknown"
|
||||
|
||||
|
||||
def test_self_chip_keeps_the_bare_star_without_our_name_or_key(powerline) -> None:
|
||||
"""A device that did not report our name or key leaves the star of a route."""
|
||||
powerline(True)
|
||||
|
||||
Reference in new issue
Block a user