Borders are grey
A plain border, the one with no caution tier, was the indigo accent: one more colour on every screen, and it meant nothing. It is now its own theme style, border, in the grey of muted, with title.border and hint.border beside it; the amber and red tiers keep their colours. That covers the base frame, every dialog, the handhelds' title bar, the tab strip, and the CLI's panels. The title bar's clip arrow for a direction with nothing more now dims to faint, since the border and muted are the same grey. On the PicoCalc the border takes the light grey slot. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
05428bd007
commit
45697db856
14 files changed
+102
-85
No files matched your search
@@ -269,7 +269,7 @@ deliberately, one at a time, and say why in the code.
|
||||
screen's results were once windowed under its actions: the page came out exactly the
|
||||
screen's height, so ↓ on the last action did nothing while `↓ n more` sat under it.
|
||||
- Grouped-list section headings use `section_heading("Label")` → `── Label ──` in the
|
||||
bold `heading` grey, never the frame's accent: a rule in the border's colour reads as
|
||||
bold `heading` grey, 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
|
||||
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
|
||||
@@ -404,7 +404,9 @@ as a grouped list does. Filling a page in is editing its `.md`; no Python follow
|
||||
- Modal popups over the pushed backdrop (never full-screen replacements).
|
||||
- Buttons are reverse-video chips (` Label `, `selected` style): safe way out on the
|
||||
left, the committing verb on the right **and default**, so Enter commits and Esc backs
|
||||
out. Two escalating caution tiers theme the frame: `danger=True` (amber) for a
|
||||
out. A plain border is the `border` grey (`title.border`/`hint.border` its title and
|
||||
hint) — never a hue: a border is chrome (JP, 2026-10-14; it was the indigo `accent`).
|
||||
Two escalating caution tiers theme the frame: `danger=True` (amber) for a
|
||||
disruptive choice — discard edits, reboot, show private key — and `destructive=True`
|
||||
(the reserved red) for irreversible **data loss**, so a delete confirm reads red like
|
||||
its `typed_confirm` sibling. Bulk or irreversible deletions gate behind `typed_confirm`
|
||||
@@ -762,8 +764,8 @@ branch on the platform per frame, and never `from meshterm.platforms import PLAT
|
||||
|
||||
- The borderless title bar (`frame._title_bar`) is this platform's whole panel border:
|
||||
`↑↓ ──── Title ─────── Esc back`. The **clip arrows lead the row and are always both
|
||||
drawn** — colour reads the scroll (the border's `accent` where that direction has more,
|
||||
`muted` where it doesn't), the same live/dim language the lane draws one row below. A
|
||||
drawn** — colour reads the scroll (the `border` grey where that direction has more,
|
||||
`faint` where it doesn't), the same live/dim language the lane draws one row below. A
|
||||
pair that appeared and vanished, half of it a blank cell, asked the reader to compare
|
||||
the row against a memory of itself; fixed furniture means the bar never changes width
|
||||
as the body scrolls either.
|
||||
|
||||
@@ -190,7 +190,7 @@ async def _status_panel(device: Device, snapshot: dict) -> Panel:
|
||||
body.append_text(value)
|
||||
if not rows:
|
||||
body = Text("no status reported", style="muted")
|
||||
return Panel(body, title="[accent]Status[/accent]", border_style="accent", expand=False)
|
||||
return Panel(body, title="[title.border]Status[/]", border_style="border", expand=False)
|
||||
|
||||
|
||||
def _firmware(info: dict) -> str:
|
||||
|
||||
@@ -971,7 +971,7 @@ class TuiUi(Ui):
|
||||
default=default,
|
||||
keys=keys,
|
||||
prompt_style=tier,
|
||||
border_style=tier or "accent",
|
||||
border_style=tier or "border",
|
||||
)
|
||||
|
||||
async def typed_confirm(self, warning: str, word: str, *, title: str = "Are you sure?") -> bool:
|
||||
|
||||
+24
-11
@@ -29,6 +29,13 @@ MESH_THEME = Theme(
|
||||
{
|
||||
"brand": "bold #5eead4",
|
||||
"accent": "bold #818cf8",
|
||||
# The border of each panel and dialog that has no caution tier: the base frame, a
|
||||
# floating dialog, the title bar of a handheld, and a tab strip. It is the grey of
|
||||
# ``muted``, and not a hue. A border is chrome, and a blue border was one more colour
|
||||
# on each screen that had no meaning (JP, 2026-10-14). The two caution tiers keep
|
||||
# their colours, because there the colour is the meaning: ``warn`` (amber) and
|
||||
# ``err`` (red).
|
||||
"border": "#a2a2a2",
|
||||
# The only reverse-video chip: the committing button of a dialog, the Abort of a
|
||||
# running action, and the insertion slot of the composer where no chip fill carries
|
||||
# it. It is a *grey* block, and the ink of the page shows through it. It was cyan
|
||||
@@ -118,7 +125,7 @@ MESH_THEME = Theme(
|
||||
# one shade brighter. Thus the title reads as part of its frame, and it is still
|
||||
# different from the frame. There is one entry for each border style that the frame
|
||||
# compositor receives (refer to theme.title_style).
|
||||
"title.accent": "bold #a5b4fc",
|
||||
"title.border": "bold #d4d4d4",
|
||||
"title.muted": "bold #d4d4d4",
|
||||
"title.warn": "bold #fcd34d",
|
||||
"title.err": "bold #fca5a5",
|
||||
@@ -128,7 +135,7 @@ MESH_THEME = Theme(
|
||||
# (and not bold). This is the opposite of the brightening of the ``title.*`` styles.
|
||||
# The hint reads as part of the frame, and it stays behind the frame. There is one
|
||||
# entry for each border style (refer to theme.hint_style).
|
||||
"hint.accent": "#6366f1",
|
||||
"hint.border": "#737373",
|
||||
"hint.muted": "#737373",
|
||||
"hint.warn": "#f59e0b",
|
||||
"hint.err": "#ef4444",
|
||||
@@ -254,9 +261,10 @@ MESH_THEME = Theme(
|
||||
#: the span completely (a light-grey unknown hash becomes the white "you", and a purple
|
||||
#: repeater becomes pink). Thus each style for a dim slot is explicitly ``not bold``
|
||||
#: (the colour is necessary, so keep it) or explicitly ``bold`` (the promotion is the
|
||||
#: intent, and only ``title.muted`` has it). A style is never silent. Never use bold on
|
||||
#: 5 and 6, because *different* meanings use their partners here (5 purple = the
|
||||
#: repeater mark, 13 pink = the node mark. 6 cyan = hint.brand, 14 bright cyan = brand).
|
||||
#: intent, and only ``title.muted`` and ``title.border`` have it). A style is never
|
||||
#: silent. Never use bold on 5 and 6, because *different* meanings use their partners
|
||||
#: here (5 purple = the repeater mark, 13 pink = the node mark. 6 cyan = hint.brand, 14
|
||||
#: bright cyan = brand).
|
||||
#: * Backgrounds can use only slots 0–7 (SGR 40–47).
|
||||
#: * The six *chromatic bright* slots (9-14) are the place where the spectrum of node
|
||||
#: names lands. They are the console rendering of the hue wheel that comes from the key
|
||||
@@ -266,7 +274,7 @@ _VT_SLOTS: tuple[tuple[int, str, str], ...] = (
|
||||
(1, "red (hint.err)", "#aa0000"),
|
||||
(2, "green (hint.ok)", "#00aa00"),
|
||||
(3, "brown/orange (hint.warn, batt.mid, heat.cool, the sensor mark)", "#aa5500"),
|
||||
(4, "blue (hint.accent, bluetooth bg)", "#0000aa"),
|
||||
(4, "blue (bluetooth bg, map.water)", "#0000aa"),
|
||||
(5, "purple (the repeater mark)", "#aa00aa"),
|
||||
(6, "cyan (hint.brand)", "#00aaaa"),
|
||||
(7, "light grey (default text, heat.cold)", "#aaaaaa"),
|
||||
@@ -349,6 +357,11 @@ MESH_THEME_16 = Theme(
|
||||
{
|
||||
"brand": "bold color(14)",
|
||||
"accent": "bold color(12)",
|
||||
# The border: the light grey, which is the nearest slot to the grey of the regular
|
||||
# theme. It is not the dark grey of ``muted``, because the title bar draws a clip
|
||||
# arrow with nothing in its direction in ``faint``, and that is slot 8. It is
|
||||
# ``not bold`` because 7 is a dim slot (refer to the bold rule).
|
||||
"border": "not bold color(7)",
|
||||
# Slot 7 is the only grey that the VT can put *behind* a reverse. Backgrounds stop at
|
||||
# the dim bank, and the dark grey of slot 8 becomes black there. It is the same
|
||||
# light grey that the F-key lane already uses to fill its chips. This is the
|
||||
@@ -378,15 +391,15 @@ MESH_THEME_16 = Theme(
|
||||
# Light grey, a step *above* the dark grey of muted. The console has only two greys,
|
||||
# and the hash of an unidentified node is content, not chrome.
|
||||
"node.unknown": "not bold color(7)",
|
||||
"title.accent": "bold color(12)",
|
||||
# The one promotion on purpose: bold moves slot 7 to 15. This title needs a colour
|
||||
# The two promotions on purpose: bold moves slot 7 to 15. These titles need a colour
|
||||
# that is almost white (the regular theme writes it as #d4d4d4).
|
||||
"title.border": "bold color(7)",
|
||||
"title.muted": "bold color(7)",
|
||||
"title.warn": "bold color(11)",
|
||||
"title.err": "bold color(9)",
|
||||
"title.ok": "bold color(10)",
|
||||
"title.brand": "bold color(14)",
|
||||
"hint.accent": "not bold color(4)",
|
||||
"hint.border": "color(8)",
|
||||
"hint.muted": "color(8)",
|
||||
"hint.warn": "not bold color(3)",
|
||||
"hint.err": "not bold color(1)",
|
||||
@@ -545,7 +558,7 @@ def title_style(border_style: str) -> str:
|
||||
|
||||
Args:
|
||||
border_style: The theme name in which the border of the panel is drawn
|
||||
(``"accent"``, ``"warn"``, and other names).
|
||||
(``"border"``, ``"warn"``, and other names).
|
||||
|
||||
Returns:
|
||||
The matching ``title.*`` theme name. If no brighter variant is defined, it is
|
||||
@@ -565,7 +578,7 @@ def hint_style(border_style: str) -> str:
|
||||
|
||||
Args:
|
||||
border_style: The theme name in which the border of the panel is drawn
|
||||
(``"accent"``, ``"warn"``, and other names).
|
||||
(``"border"``, ``"warn"``, and other names).
|
||||
|
||||
Returns:
|
||||
The matching ``hint.*`` theme name. If no variant is defined, it is ``"muted"``.
|
||||
|
||||
+12
-12
@@ -206,7 +206,7 @@ def _panel_box(
|
||||
visible: The ANSI lines of the viewport, already sliced and padded to height.
|
||||
more_above: Whether content continues above the slice.
|
||||
more_below: Whether content continues below the slice.
|
||||
border: The name of the border style (``"accent"`` for the focused screen).
|
||||
border: The name of the border style (``"border"`` for the focused screen).
|
||||
caption: A short muted credit for the right end of the bottom rule (empty for none).
|
||||
flush: Remove the one-cell padding inside each side border, for a body that fills
|
||||
the frame (refer to :attr:`~meshterm.ui.tui.screen.Screen.flush`).
|
||||
@@ -309,15 +309,15 @@ def _title_bar(
|
||||
memory of it, to learn something that the row can state directly. (When only one arrow
|
||||
showed, a blank cell stood in for the other.) Thus the pair is fixed at the left edge,
|
||||
where the eye starts, and **colour** gives the information. An arrow whose direction
|
||||
has more content takes the ``accent`` of the border, so it looks like part of the
|
||||
frame. An arrow with nothing in its direction becomes ``muted``. This is the same
|
||||
has more content takes the grey of the border, so it looks like part of the frame. An
|
||||
arrow with nothing in its direction becomes ``faint``, a step darker. This is the same
|
||||
live/dim language that the F-key lane draws one row below (a dim chip keeps its label
|
||||
and loses its fill). The two are the only indicators of the platform.
|
||||
|
||||
The title is centred in the rule between the arrows and the tail, exactly as the Rich
|
||||
``Panel`` centres its own ``title``. (That panel is the frame with a border on the
|
||||
desktop, which this bar replaces.) The rule itself takes ``border_style``
|
||||
(``"accent"``) directly: the same bold weight that a real border has. It does not take
|
||||
(``"border"``) directly: the same style that a real border has. It does not take
|
||||
the muted variant of :func:`~meshterm.ui.theme.hint_style`, which is for secondary text
|
||||
next to a border (the Esc atom below, a footer hint on the other platform).
|
||||
|
||||
@@ -350,7 +350,7 @@ def _title_bar(
|
||||
status: The atoms of the header, for a platform without a header row. ``None`` (or
|
||||
empty: nothing unread, no pack) draws the bar exactly as it is with a header.
|
||||
"""
|
||||
border = "accent"
|
||||
border = "border"
|
||||
head_span = 3 # the arrow pair, plus the space between it and the rule
|
||||
status = status if status is not None and status.cell_len else None
|
||||
stat_span = status.cell_len + 1 if status else 0 # the space before the status
|
||||
@@ -371,8 +371,8 @@ def _title_bar(
|
||||
label_w = heading.cell_len + 2
|
||||
|
||||
bar = Text()
|
||||
bar.append("↑", style=border if more_above else "muted")
|
||||
bar.append("↓", style=border if more_below else "muted")
|
||||
bar.append("↑", style=border if more_above else "faint")
|
||||
bar.append("↓", style=border if more_below else "faint")
|
||||
bar.append(" ")
|
||||
if title:
|
||||
left = max(1, (rule_span - label_w) // 2)
|
||||
@@ -472,7 +472,7 @@ def compose_base(
|
||||
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(title, visible, more_above, more_below, "accent", caption, flush)
|
||||
panel = _panel_box(title, visible, more_above, more_below, "border", caption, flush)
|
||||
body = render_lines(Group(panel, footer_row()), cols)
|
||||
if footer_lane is None:
|
||||
_BASE_BOX_CACHE = (key, body)
|
||||
@@ -695,16 +695,16 @@ def compose_startup(screen: Screen, cols: int, rows: int) -> str:
|
||||
visible, more_above, more_below = _visible_slice(screen, body_lines, viewport)
|
||||
|
||||
body = Text.from_ansi("\n".join(visible))
|
||||
hint = hint_style("accent")
|
||||
hint = hint_style("border")
|
||||
subtitle = f"[{hint}]{hint_text}[/]"
|
||||
if more_above or more_below:
|
||||
arrow = ("↑" if more_above else " ") + ("↓" if more_below else " ")
|
||||
subtitle = f"[{hint}]{arrow} · {fit_hint(hint_text, inner_w - 5)}[/]"
|
||||
panel = Panel(
|
||||
body,
|
||||
title=f"[{title_style('accent')}]{screen.title}[/]" if screen.title else None,
|
||||
title=f"[{title_style('border')}]{screen.title}[/]" if screen.title else None,
|
||||
subtitle=subtitle,
|
||||
border_style="accent",
|
||||
border_style="border",
|
||||
padding=(vpad, 1),
|
||||
width=inner_w + 4,
|
||||
)
|
||||
@@ -865,7 +865,7 @@ def compose_dialog(screen: Screen, cols: int, rows: int) -> str:
|
||||
"""
|
||||
max_w, vpad, viewport, body_lines = _dialog_layout(screen, cols, rows)
|
||||
visible, more_above, more_below = _visible_slice(screen, body_lines, viewport)
|
||||
border = getattr(screen, "border_style", "accent")
|
||||
border = getattr(screen, "border_style", "border")
|
||||
# The resolved hint, not the hint of the screen: it is what the border draws. It changes
|
||||
# with the lane below, and also with the hint of the screen (refer to _dialog_hint).
|
||||
hint = _dialog_hint(screen)
|
||||
|
||||
@@ -31,9 +31,9 @@ from rich.text import Text
|
||||
from .render import render_to_ansi
|
||||
from .spinner import Spinner
|
||||
|
||||
#: The colour of the title line (the theme's ``title.accent``, in hex so that the fade can
|
||||
#: dim it).
|
||||
_TITLE_COLOR = "bold #a5b4fc"
|
||||
#: The colour of the title line (the theme's ``title.border``, in hex so that the fade can
|
||||
#: dim it). It is the title of a dialog, which the card stands in for.
|
||||
_TITLE_COLOR = "bold #d4d4d4"
|
||||
|
||||
#: The colour of the working chip (the theme's ``accent``). The chip has the same one-cell
|
||||
#: spinner as the other dialogs.
|
||||
|
||||
@@ -539,7 +539,7 @@ class ButtonDialog(_KeylessDialog):
|
||||
prompt_style: str = "",
|
||||
button_style: str = "selected",
|
||||
button_idle_style: str = "muted",
|
||||
border_style: str = "accent",
|
||||
border_style: str = "border",
|
||||
lane: Sequence[FPair | None] | None = None,
|
||||
) -> None:
|
||||
"""Build a button dialog.
|
||||
|
||||
@@ -488,8 +488,8 @@ def _message_border(message: Text | str) -> str:
|
||||
A note about a result has its severity as theme spans (``[err]``, ``[warn]``, ``[ok]``).
|
||||
Thus the border of the dialog can show the same severity. An error message gets the
|
||||
``err`` border. A warning (for example "device rebooting") gets the caution border. All
|
||||
the other messages, also the success messages, get the standard accent. A plain string
|
||||
has no spans, and it is always neutral.
|
||||
the other messages, also the success messages, get the grey of the standard border. A
|
||||
plain string has no spans, and it is always neutral.
|
||||
|
||||
Args:
|
||||
message: The message of the dialog, with styles or plain.
|
||||
@@ -503,7 +503,7 @@ def _message_border(message: Text | str) -> str:
|
||||
return "err"
|
||||
if "warn" in styles:
|
||||
return "warn"
|
||||
return "accent"
|
||||
return "border"
|
||||
|
||||
|
||||
class Visit:
|
||||
@@ -1565,7 +1565,7 @@ class TuiSession:
|
||||
prompt_style: str = "",
|
||||
button_style: str = "selected",
|
||||
button_idle_style: str = "muted",
|
||||
border_style: str = "accent",
|
||||
border_style: str = "border",
|
||||
lane: Sequence[fkeys.FPair | None] | None = None,
|
||||
) -> Any:
|
||||
"""Show a centred button dialog. Return the chosen value, or ``None`` if cancelled.
|
||||
@@ -1654,7 +1654,7 @@ class TuiSession:
|
||||
("✓ flood advertisement sent") is too small for a full result screen. Thus it floats
|
||||
as a small dialog over the screen below it. Enter (OK) or Esc closes it. The border
|
||||
takes the strongest tone of the message (refer to :func:`_message_border`). Thus an
|
||||
error shows in red, and a success stays in the standard accent. The method calls
|
||||
error shows in red, and a success stays in the grey of the standard border. The method calls
|
||||
:meth:`button_dialog`, which floats the dialog over a blank base when the stack is
|
||||
empty (a tool that runs directly from the menu, which is popped while the tool
|
||||
runs).
|
||||
|
||||
+27
-25
@@ -172,7 +172,7 @@ def banner(
|
||||
body = Text.from_markup(
|
||||
f"[brand]MeshTerm[/brand] [muted]v{__version__}[/muted]\n[muted]device:[/muted] {target}"
|
||||
)
|
||||
return Panel(body, border_style="accent", expand=False, title="[accent]Mesh[/accent]")
|
||||
return Panel(body, border_style="border", expand=False, title="[title.border]Mesh[/]")
|
||||
|
||||
|
||||
def make_progress(console: Console) -> Progress:
|
||||
@@ -1108,7 +1108,7 @@ def stats_panel(
|
||||
_hop_medians_table(stats.hop_snrs, device_label, resolve, hash_bytes, device_hash)
|
||||
)
|
||||
body: Text | Group = sections[0] if len(sections) == 1 else Group(*sections)
|
||||
return Panel(body, title="[accent]Trace summary[/accent]", border_style="accent", expand=False)
|
||||
return Panel(body, title="[title.border]Trace summary[/]", border_style="border", expand=False)
|
||||
|
||||
|
||||
# The glyphs and colours of the node types. They are the same as the marker palette of the
|
||||
@@ -1514,8 +1514,9 @@ def tab_strip(labels: Sequence[str], active: int, width: int, *, compact: bool =
|
||||
This is the only navigation header for a screen that shows one view of the full height
|
||||
at a time, between a few named views (for example ``Info`` and ``Routes`` on the node
|
||||
detail page), and does not stack them. Each tab always has a box with a full top. The
|
||||
box has a dim ``faint`` outline by default, and a lit ``accent`` outline when the tab
|
||||
is active. It is not only the active tab that has a box. Thus the width of each tab is
|
||||
box has a dim ``faint`` outline by default, and the grey ``border`` outline when the
|
||||
tab is active. The label of the active tab is lit as the title of a panel is
|
||||
(``title.border``). It is not only the active tab that has a box. Thus the width of each tab is
|
||||
fixed, in any selection, and a switch of tabs never shifts the labels that follow it.
|
||||
Neighbouring tabs share a vertical line (one glyph, not two). Each shared *top* corner
|
||||
is drawn as if the tab on the left sits on top of the tab on the right. It "opens"
|
||||
@@ -1524,7 +1525,7 @@ def tab_strip(labels: Sequence[str], active: int, width: int, *, compact: bool =
|
||||
left) in all other places. Thus the active tab is the one exception. It always wins both
|
||||
of its top corners, and it looks lifted in front of its neighbours.
|
||||
|
||||
The *bottom* border is a single continuous rule with the accent colour. It is one line,
|
||||
The *bottom* border is a single continuous rule in the colour of the border. It is one line,
|
||||
so it has one colour throughout, the colour of the active tab. The inactive tabs sit
|
||||
flush against it. They have no corners or seams of their own there. The rule only
|
||||
passes behind them. It is broken only under the active tab, and thus the tab looks
|
||||
@@ -1532,7 +1533,7 @@ def tab_strip(labels: Sequence[str], active: int, width: int, *, compact: bool =
|
||||
from the west, and turns north into the wall of the tab). It leaves the width of the
|
||||
active tab open (no line, because that gap *is* the start of the page). Then it turns
|
||||
back down through ``╰`` (the wall meets the flat run that continues east) and goes on
|
||||
flat. A lone tab becomes the plain accent heading (the ``── Label ──`` form of
|
||||
flat. A lone tab becomes a plain heading in the border grey (the ``── Label ──`` form of
|
||||
:func:`~meshterm.ui.menus.section_heading`), because there is nothing to switch
|
||||
between or to layer. When ``width`` has more space than necessary, the tab boxes (the
|
||||
top row and the label row) are two columns from the left. The bottom rule is one
|
||||
@@ -1560,18 +1561,20 @@ def tab_strip(labels: Sequence[str], active: int, width: int, *, compact: bool =
|
||||
if not labels:
|
||||
return Group(Text(""))
|
||||
if len(labels) == 1:
|
||||
return Group(Text(f"── {labels[0]} ──", style="accent", no_wrap=True))
|
||||
return Group(Text(f"── {labels[0]} ──", style="border", no_wrap=True))
|
||||
if compact:
|
||||
row = Text("──", style="accent", no_wrap=True)
|
||||
row = Text("──", style="border", no_wrap=True)
|
||||
for i, label in enumerate(labels):
|
||||
if i == active:
|
||||
row.append(f"┤ {label} ├", style="accent")
|
||||
row.append("┤ ", style="border")
|
||||
row.append(label, style="title.border")
|
||||
row.append(" ├", style="border")
|
||||
else:
|
||||
row.append("─", style="accent")
|
||||
row.append("─", style="border")
|
||||
row.append(f" {label} ", style="faint")
|
||||
row.append("─", style="accent")
|
||||
row.append("─", style="accent")
|
||||
row.append("─" * max(0, width - len(row.plain)), style="accent")
|
||||
row.append("─", style="border")
|
||||
row.append("─", style="border")
|
||||
row.append("─" * max(0, width - len(row.plain)), style="border")
|
||||
return Group(row)
|
||||
|
||||
n = len(labels)
|
||||
@@ -1585,14 +1588,14 @@ def tab_strip(labels: Sequence[str], active: int, width: int, *, compact: bool =
|
||||
return junction if junction == 0 or junction == active else junction - 1
|
||||
|
||||
def top_style(junction: int) -> str:
|
||||
return "accent" if top_owner(junction) == active else "faint"
|
||||
return "border" if top_owner(junction) == active else "faint"
|
||||
|
||||
def bot_glyph(junction: int) -> str:
|
||||
"""The glyph of the bottom rule at this junction.
|
||||
|
||||
It is a corner that turns into or out of the wall of the active tab, or else a flat
|
||||
pass-through. It always has the accent colour. Refer to the enclosing docstring for
|
||||
the reason.
|
||||
pass-through. It always has the colour of the border. Refer to the enclosing docstring
|
||||
for the reason.
|
||||
"""
|
||||
if junction == active:
|
||||
return "╯"
|
||||
@@ -1602,20 +1605,19 @@ def tab_strip(labels: Sequence[str], active: int, width: int, *, compact: bool =
|
||||
|
||||
top = Text(pad, no_wrap=True)
|
||||
mid = Text(pad, no_wrap=True)
|
||||
bot = Text("─" * margin, style="accent", no_wrap=True)
|
||||
bot = Text("─" * margin, style="border", no_wrap=True)
|
||||
for i, _label in enumerate(labels):
|
||||
opens = i == 0 or i == active
|
||||
top.append("╭" if opens else "╮", style=top_style(i))
|
||||
mid.append("│", style=top_style(i))
|
||||
bot.append(bot_glyph(i), style="accent")
|
||||
tab_style = "accent" if i == active else "faint"
|
||||
top.append("─" * len(inner[i]), style=tab_style)
|
||||
mid.append(inner[i], style=tab_style)
|
||||
bot.append(" " * len(inner[i]) if i == active else "─" * len(inner[i]), style="accent")
|
||||
bot.append(bot_glyph(i), style="border")
|
||||
top.append("─" * len(inner[i]), style="border" if i == active else "faint")
|
||||
mid.append(inner[i], style="title.border" if i == active else "faint")
|
||||
bot.append(" " * len(inner[i]) if i == active else "─" * len(inner[i]), style="border")
|
||||
top.append("╮", style=top_style(n))
|
||||
mid.append("│", style=top_style(n))
|
||||
bot.append(bot_glyph(n), style="accent")
|
||||
bot.append("─" * max(0, width - len(bot.plain)), style="accent")
|
||||
bot.append(bot_glyph(n), style="border")
|
||||
bot.append("─" * max(0, width - len(bot.plain)), style="border")
|
||||
return Group(top, mid, bot)
|
||||
|
||||
|
||||
@@ -1778,7 +1780,7 @@ def tx_opt_summary(result: TxOptResult) -> Panel:
|
||||
Text.from_markup(applied),
|
||||
)
|
||||
return Panel(
|
||||
body, title="[accent]TX optimization[/accent]", border_style="accent", expand=False
|
||||
body, title="[title.border]TX optimization[/]", border_style="border", expand=False
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -315,7 +315,7 @@ def test_the_popup_carries_its_tone_as_spans_not_as_markup(ctx: AppContext, tmp_
|
||||
from meshterm.ui.diagnostics import SAVE_KEY
|
||||
from meshterm.ui.tui.session import _message_border
|
||||
|
||||
for target, expected in ((tmp_path / "ok.md", "accent"), (_blocked(tmp_path), "err")):
|
||||
for target, expected in ((tmp_path / "ok.md", "border"), (_blocked(tmp_path), "err")):
|
||||
session = _Session(keep=True)
|
||||
page = _page(ctx, session=session, save_path=target)
|
||||
page.handle("text", SAVE_KEY)
|
||||
|
||||
@@ -153,9 +153,9 @@ def test_tab_strip_bottom_is_a_continuous_rule_notched_at_the_active_tab() -> No
|
||||
# (west and north, ╯) into "Routes". It is blank across the width of "Routes" (open into
|
||||
# the page). Then it goes down again (north and east, ╰) and continues.
|
||||
assert bot.plain == "────────╯ ╰"
|
||||
# It is one continuous line: each cell has the accent, the colour of the active tab. The
|
||||
# colours are not mixed.
|
||||
assert {r.style for r in bot.spans} == {"accent"}
|
||||
# It is one continuous line: each cell has the grey of the border, the colour of the
|
||||
# active tab. The colours are not mixed.
|
||||
assert {r.style for r in bot.spans} == {"border"}
|
||||
|
||||
_, _, bot_first = tab_strip(["Map", "Routes"], 0, width=20).renderables
|
||||
assert bot_first.plain == "╯ ╰───────────" # the notch is now under the first tab
|
||||
|
||||
@@ -103,27 +103,27 @@ def test_the_clip_arrows_are_always_drawn_and_say_it_in_colour() -> None:
|
||||
The arrows once appeared and disappeared. A pair that was half shown left a blank cell
|
||||
for the missing arrow, and the user had to compare the row with a memory of the row
|
||||
(JP, 2026-08-31). Now the pair never moves and never changes width. An arrow for a
|
||||
direction with more content has the accent colour of the border. An arrow for a
|
||||
direction with no more content is muted. This is the live and dim language of the F-key
|
||||
lane, one row up.
|
||||
direction with more content has the grey of the border. An arrow for a direction with
|
||||
no more content is ``faint``, a step darker. This is the live and dim language of the
|
||||
F-key lane, one row up.
|
||||
"""
|
||||
hint = "↑↓ move · Esc back"
|
||||
assert _arrow_styles(_bar_text("Contacts", hint, above=True, below=True)) == (
|
||||
"accent",
|
||||
"accent",
|
||||
"border",
|
||||
"border",
|
||||
)
|
||||
assert _arrow_styles(_bar_text("Contacts", hint, above=False, below=True)) == (
|
||||
"muted",
|
||||
"accent",
|
||||
"faint",
|
||||
"border",
|
||||
)
|
||||
assert _arrow_styles(_bar_text("Contacts", hint, above=True, below=False)) == (
|
||||
"accent",
|
||||
"muted",
|
||||
"border",
|
||||
"faint",
|
||||
)
|
||||
# A body that fits keeps the pair, and both are dim. Nothing appears or disappears.
|
||||
assert _arrow_styles(_bar_text("Contacts", hint, above=False, below=False)) == (
|
||||
"muted",
|
||||
"muted",
|
||||
"faint",
|
||||
"faint",
|
||||
)
|
||||
|
||||
widths = {
|
||||
|
||||
@@ -211,9 +211,9 @@ def test_message_border_echoes_the_strongest_tone() -> None:
|
||||
both = Text.from_markup("[warn]careful[/warn] [err]broken[/err]")
|
||||
assert _message_border(err) == "err"
|
||||
assert _message_border(warn) == "warn"
|
||||
assert _message_border(ok) == "accent"
|
||||
assert _message_border(ok) == "border"
|
||||
assert _message_border(both) == "err"
|
||||
assert _message_border("plain string") == "accent"
|
||||
assert _message_border("plain string") == "border"
|
||||
|
||||
|
||||
# -- the caution tiers of the button dialog ----------------------------------------------
|
||||
@@ -265,6 +265,6 @@ async def test_dialog_plain_is_neutral_and_destructive_outranks_danger() -> None
|
||||
With no flag, the frame has the neutral accent.
|
||||
"""
|
||||
plain = await _dialog_styles()
|
||||
assert plain["border_style"] == "accent" and plain["prompt_style"] == ""
|
||||
assert plain["border_style"] == "border" and plain["prompt_style"] == ""
|
||||
both = await _dialog_styles(danger=True, destructive=True)
|
||||
assert both["border_style"] == "err" and both["prompt_style"] == "err"
|
||||
+4
-4
@@ -1309,7 +1309,7 @@ def _cell_color(line: str, idx: int) -> tuple[int, int, int]:
|
||||
return (triplet.red, triplet.green, triplet.blue)
|
||||
|
||||
|
||||
_ACCENT = (129, 140, 248) # The accent border of the theme, #818cf8.
|
||||
_BORDER = (162, 162, 162) # The border grey of the theme, #a2a2a2.
|
||||
|
||||
|
||||
def _find(plain: str, glyphs: frozenset[str]) -> int:
|
||||
@@ -1369,13 +1369,13 @@ def test_a_frame_draws_in_one_colour() -> None:
|
||||
flat. The test checks the nested panels with the outer frame, because the pass also
|
||||
lit those.
|
||||
"""
|
||||
inner = Panel(Text("body"), border_style="accent", width=20)
|
||||
inner = Panel(Text("body"), border_style="border", width=20)
|
||||
screen = ScrollScreen(Group(Text("above"), inner), title="outer")
|
||||
lines = frame.compose_base(Text("h"), screen, "hint", 60, 20).split("\n")
|
||||
assert _border_colours(lines) == {_ACCENT}, "the base frame draws in more than one colour"
|
||||
assert _border_colours(lines) == {_BORDER}, "the base frame draws in more than one colour"
|
||||
|
||||
dialog = frame.compose_dialog(ScrollScreen(Text("body"), title="d"), 80, 20)
|
||||
assert _border_colours(dialog.split("\n")) == {_ACCENT}, "so does a dialog's"
|
||||
assert _border_colours(dialog.split("\n")) == {_BORDER}, "so does a dialog's"
|
||||
|
||||
|
||||
# --- prompts -----------------------------------------------------------------
|
||||
|
||||
Reference in new issue
Block a user