Section headings carry no icon
Chat's Channels, Rooms, and Direct headings and the trophy case's discipline headings drop their emoji, for a cleaner look. The trophy marks stay on the delete list's rows and the record page's header. section_heading no longer strips icons, since no heading has one. Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
820740c9a0
commit
eb2d314551
6 files changed
+25
-34
No files matched your search
@@ -271,7 +271,9 @@ deliberately, one at a time, and say why in the code.
|
||||
- Grouped-list section headings use `section_heading("Label")` → `── Label ──` in the
|
||||
bold `heading` style — the frame title's colour, `title.border` (JP, 2026-10-15) —
|
||||
never the `border` grey: a rule in the border's colour reads as
|
||||
part of the frame. A chat's day divider wears the same dress. That is also what makes a heading *sticky* (it pins to the top row while its section
|
||||
part of the frame. A heading is words only, never an icon — icons live in rows (JP,
|
||||
2026-10-15: Chat's `📡 Channels` and the trophy case's marks came off for a cleaner
|
||||
look). A chat's day divider wears the same dress. That is also what makes a heading *sticky* (it pins to the top row while its section
|
||||
scrolls, and the ^PgUp/^PgDn jumps step by it), so build them through it — a hand-rolled
|
||||
`Separator` is no landmark. Prose written *directly under* a heading (a description of
|
||||
what the section holds) is its preamble and pins with it, in order, as each row scrolls
|
||||
|
||||
@@ -252,7 +252,7 @@ class ChatTool(Tool):
|
||||
# Refer to Screen.sticky_rows.
|
||||
items: list = [
|
||||
Separator(lambda w: _picker_header(lanes, w), pinned=True),
|
||||
section_heading("📡 Channels"),
|
||||
section_heading("Channels"),
|
||||
]
|
||||
for conversation in channels:
|
||||
items.append(
|
||||
@@ -264,7 +264,7 @@ class ChatTool(Tool):
|
||||
)
|
||||
)
|
||||
|
||||
items.append(section_heading("📌 Rooms"))
|
||||
items.append(section_heading("Rooms"))
|
||||
for conversation in rooms:
|
||||
items.append(
|
||||
Choice(
|
||||
@@ -281,7 +281,7 @@ class ChatTool(Tool):
|
||||
# a room?", and an empty section tells them where to go.
|
||||
items.append(Separator(" no rooms joined — join one on the Rooms page"))
|
||||
|
||||
items.append(section_heading("👤 Direct"))
|
||||
items.append(section_heading("Direct"))
|
||||
if companions:
|
||||
for conversation in direct:
|
||||
items.append(
|
||||
|
||||
@@ -650,10 +650,9 @@ def section_heading(label: str) -> Separator:
|
||||
to heading. A row built by hand does not get this behaviour. Thus always use this
|
||||
function.
|
||||
|
||||
A heading that starts with an icon (``📡 Channels``, the mark of a trophy discipline)
|
||||
loses the icon on a platform that draws no icon lane, the same as a command row. A
|
||||
heading is the same type of label. Also, a substitute glyph next to the rule, which
|
||||
already draws ``──``, looks like noise (refer to :func:`command_label`).
|
||||
A heading is words only, with no icon. Icons go on rows. The headings of Chat
|
||||
(``📡 Channels``) and of the trophy case (the mark of each discipline) had icons. We
|
||||
removed them for a cleaner look (JP, 2026-10-15).
|
||||
|
||||
Grey, never the accent of the frame: a rule in the colour of the border looks like a
|
||||
part of the frame, not like a heading in it.
|
||||
@@ -663,7 +662,7 @@ def section_heading(label: str) -> Separator:
|
||||
:attr:`~meshterm.ui.tui.select.Separator.spaced`). Thus a list does not put a blank
|
||||
separator before a heading for space.
|
||||
"""
|
||||
return Separator(f"── {command_label(label)} ──", style="heading", heading=True, spaced=True)
|
||||
return Separator(f"── {label} ──", style="heading", heading=True, spaced=True)
|
||||
|
||||
|
||||
def fit_cells(text: str, width: int, *, align: str = "left") -> str:
|
||||
|
||||
@@ -127,9 +127,11 @@ _LABEL_W = 12
|
||||
def discipline_label(category: Category, lane: int) -> str:
|
||||
"""The mark of one discipline, padded to ``lane`` cells, then its title.
|
||||
|
||||
This is the only way to write the name of a board. Thus the seven headings (and the
|
||||
seven rows of the list that selects a discipline to delete) start their titles in one
|
||||
column, not in two. The marks do not all have the same width. ``🛣`` and ``🕸`` are
|
||||
This is the only way to write the name of a board with its mark: on the seven rows of
|
||||
the list that selects a discipline to delete, and on the header of the record page.
|
||||
Thus the seven rows start their titles in one column, not in two. A board heading has
|
||||
no mark. It is only the title (refer to :func:`~meshterm.ui.menus.section_heading`).
|
||||
The marks do not all have the same width. ``🛣`` and ``🕸`` are
|
||||
not in Emoji_Presentation, and they are one cell wide, while the other five are two
|
||||
cells wide. Because of this, ``🛣 Longest distance`` was one column away from
|
||||
``🎯 Farthest node``. Its gap also looked closed on a terminal that paints the road
|
||||
@@ -157,7 +159,7 @@ def discipline_lane() -> int:
|
||||
|
||||
Refer to :func:`discipline_label`. The width is measured, never written as a constant.
|
||||
The marks change with the platform. If a substitution becomes narrower later, each
|
||||
heading becomes one cell narrower, and nothing else moves.
|
||||
label becomes one cell narrower, and nothing else moves.
|
||||
"""
|
||||
return max(cell_len(glyph(category.icon)) for category in CATEGORIES)
|
||||
|
||||
@@ -448,7 +450,7 @@ class RecordScreen(Screen):
|
||||
|
||||
The record page has this line where the node page has its identity header. It
|
||||
reads as one sentence: ``🛣 Longest distance — 5th place: 112.0 km``. The
|
||||
discipline is written the same as its board heading writes it
|
||||
discipline is written the same as its row in the delete list writes it
|
||||
(:func:`discipline_label`: the mark is padded to the measured lane, so that ``🛣``
|
||||
and ``🎯`` start their titles in the same cell). The standing is the rank of the
|
||||
record on that board. The metric is the score in the unit of the discipline
|
||||
@@ -913,10 +915,6 @@ async def open_records(ctx: AppContext) -> dict:
|
||||
# node is matched against these keys.
|
||||
target_counts = ctx.repo.target_trace_counts()
|
||||
|
||||
# The mark column after which each board heading writes its title (refer to
|
||||
# discipline_label).
|
||||
mark_lane = discipline_lane()
|
||||
|
||||
# The node positions and types for the area drawing of a record. They are collected
|
||||
# live, the same as the trace tools collect them to score a walk (refer to
|
||||
# node_geo_lookup). The entry list does not change while the screen is open.
|
||||
@@ -1001,7 +999,7 @@ async def open_records(ctx: AppContext) -> dict:
|
||||
|
||||
def describe(category: Category) -> list:
|
||||
"""The heading and its wrapped description, as rows that the user cannot select."""
|
||||
rows: list = [section_heading(discipline_label(category, mark_lane))]
|
||||
rows: list = [section_heading(category.title)]
|
||||
desc = category.description
|
||||
if category.needs_positions and self_pos is None:
|
||||
desc += " — needs your location (set it in Config) to score"
|
||||
|
||||
+2
-2
@@ -2025,7 +2025,7 @@ async def test_picker_lists_companions_only(repo: Repository) -> None:
|
||||
async def test_picker_pins_its_column_header_over_the_group_heading(
|
||||
repo: Repository,
|
||||
) -> None:
|
||||
"""On a page scrolled into Direct, the lane names stay at the top, over ``👤 Direct``."""
|
||||
"""On a page scrolled into Direct, the lane names stay at the top, over ``Direct``."""
|
||||
from types import SimpleNamespace
|
||||
|
||||
from meshterm.tools.chat import ChatTool
|
||||
@@ -2049,7 +2049,7 @@ async def test_picker_pins_its_column_header_over_the_group_heading(
|
||||
top = [ansi.sub("", row).strip() for row in visible[:2]]
|
||||
# Short words over a badge and an age, then the message.
|
||||
assert top[0].split() == ["CONVERSATION", "NEW", "LAST", "MESSAGE"]
|
||||
assert top[1] == "── 👤 Direct ──"
|
||||
assert top[1] == "── Direct ──"
|
||||
assert above is True
|
||||
# If the width is too small for the whole line, the last label gets shorter and does
|
||||
# not wrap. The lane has the size of these six-cell names, so this starts at a width
|
||||
|
||||
@@ -392,12 +392,10 @@ def test_record_header_names_the_discipline_the_standing_and_the_metric() -> Non
|
||||
"""One line above the tabs: ``<mark> <discipline> — <nth> place: <score>``.
|
||||
|
||||
This is the identity header of the node page, for a record. The line writes the
|
||||
discipline in the same way as its board heading. The mark has padding to the measured
|
||||
discipline in the same way as its row in the delete list. The mark has padding to the measured
|
||||
lane, so a ``🛣`` of one cell and a ``🎯`` of two cells start their titles in the same
|
||||
cell. The score lane that repeated the metric on Info does not exist now.
|
||||
"""
|
||||
from meshterm.ui.records_screen import discipline_label, discipline_lane
|
||||
|
||||
screen = _dialog(_record(), shape=_loop_shape())
|
||||
lines = _plain(screen.render_body(58)).splitlines()
|
||||
grand_tour = CATEGORY_BY_ID["grand_tour"]
|
||||
@@ -416,7 +414,7 @@ def test_record_header_names_the_discipline_the_standing_and_the_metric() -> Non
|
||||
head = _plain(_dialog(partial, rank=5).render_body(58)).splitlines()[0]
|
||||
assert head.endswith("Longest distance — 5th place: ≥ 8.4 km") # the board spelling of a bound
|
||||
|
||||
# The mark lane is the lane of the board heading: the titles of the two marks start in one cell.
|
||||
# The mark lane is the lane of the delete list: the titles of the two marks start in one cell.
|
||||
long_haul = CATEGORY_BY_ID["long_haul"]
|
||||
assert cell_len(discipline_label(long_haul, discipline_lane()).split("Longest")[0]) == cell_len(
|
||||
discipline_label(grand_tour, discipline_lane()).split("Most")[0]
|
||||
@@ -828,14 +826,8 @@ async def test_the_board_pins_its_discipline_heading_and_description(tui_ctx) ->
|
||||
blocks = [
|
||||
[_plain([line]).strip() for line in rows] for _idx, rows in browser._sticky_headers
|
||||
]
|
||||
lane = discipline_lane()
|
||||
assert [rows[0] for rows in blocks] == [
|
||||
f"── {discipline_label(c, lane)} ──" for c in CATEGORIES
|
||||
]
|
||||
# Also, each of them starts its title in the same column. The marks do not all have
|
||||
# the same width (🛣 and 🕸 are one cell, and the others are two), so the lane pads.
|
||||
starts = {cell_len(discipline_label(c, lane)) - cell_len(c.title) for c in CATEGORIES}
|
||||
assert len(starts) == 1
|
||||
# A heading is the title only. The mark of the discipline stays on its rows.
|
||||
assert [rows[0] for rows in blocks] == [f"── {c.title} ──" for c in CATEGORIES]
|
||||
assert all(len(rows) >= 2 for rows in blocks) # each carries its description too
|
||||
# A highlight deep in the one board that has records scrolls its heading off the top
|
||||
# of a short viewport. The heading is the first item that pins, and its description
|
||||
@@ -844,7 +836,7 @@ async def test_the_board_pins_its_discipline_heading_and_description(tui_ctx) ->
|
||||
browser.handle("down")
|
||||
visible, above, _below = frame._visible_slice(browser, browser.render_body(72), 10)
|
||||
board = CATEGORY_BY_ID["grand_tour"]
|
||||
assert _plain([visible[0]]).strip() == f"── {discipline_label(board, lane)} ──"
|
||||
assert _plain([visible[0]]).strip() == f"── {board.title} ──"
|
||||
assert _plain([visible[1]]).strip().startswith(board.description[:20])
|
||||
assert above is True
|
||||
browser.resolve(None) # the Esc key
|
||||
|
||||
Reference in new issue
Block a user