Node detail: fix tab-strip corners, dim inactive tabs, one rule colour

- Swap the active tab's bottom corners: the flat rule turning up into
  the tab's wall is west+north (╯), and turning back down out of it is
  north+east (╰) — had these backwards.
- The left margin belongs to the tab boxes, not the bottom rule: the
  rule is one continuous line, so it now runs the full width including
  through the margin instead of being indented with the tabs.
- Inactive tab outlines move from "muted" to the dimmer "faint" shade.
- The whole bottom rule is now a single colour (accent, the active
  tab's) rather than mixing accent/faint segments — it's one line.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Jean-Pierre MartineauandClaude Sonnet 5 committed 2026-07-26 14:02:52 -04:00
1 parent d1c2df61c9
commit 5ff8eae74b
2 files changed
+60 -43

No files matched your search

+36 -34
View File
@@ -914,26 +914,30 @@ def tab_strip(labels: Sequence[str], active: int, width: int) -> Group:
THE navigation header for a screen that pages one full-height view at a time between a
handful of named views (the node detail page's ``Info`` / ``Routes``) instead of stacking
them. Every tab is always drawn as a full-topped box — muted by default, accent when
active — instead of only the active one; this keeps every tab's width fixed regardless
of which is selected, so switching tabs never shifts the labels that come after it. Tabs
share a vertical between neighbours (one glyph, not two), and each shared *top* corner is
drawn as if the tab to the left sits on top of the tab to the right: it "opens" (rounds
toward the tab starting there) only at the very first tab and at the active tab's own
left edge, and "closes" (rounds back, ceding the position to the tab on its left)
everywhere else — so the active tab is the one exception that always wins both of its top
corners, reading as lifted in front of its neighbours.
them. Every tab is always drawn as a full-topped box — a dim ``faint`` outline by
default, lit ``accent`` when active — instead of only the active one; this keeps every
tab's width fixed regardless of which is selected, so switching tabs never shifts the
labels that come after it. Tabs share a vertical between neighbours (one glyph, not
two), and each shared *top* corner is drawn as if the tab to the left sits on top of the
tab to the right: it "opens" (rounds toward the tab starting there) only at the very
first tab and at the active tab's own left edge, and "closes" (rounds back, ceding the
position to the tab on its left) everywhere else — so the active tab is the one exception
that always wins both of its top corners, reading as lifted in front of its neighbours.
The *bottom* border is a single continuous rule the inactive tabs sit flush against —
they get no corners or seams of their own down there, just plain muted line passing
behind them — broken only under the active tab, which is why it reads as merged into the
page below it: the rule steps up into ``╰``, leaves the active tab's width open (no
line — that gap *is* the page starting), then steps back down through ``╯`` and carries
on flat. A lone tab collapses to the plain accent heading
(:func:`~meshterm.ui.menus.section_heading`'s ``── Label ──`` form) since there is
nothing to switch between or layer. When there's slack in ``width``, the whole strip
sits two columns in from the left rather than flush against it. The owning screen
switches the active index (``Tab``/``Shift+Tab``); the strip itself is pure presentation.
The *bottom* border is a single continuous accent-coloured rule — it's one line, so it
carries one colour throughout, the active tab's — that the inactive tabs sit flush
against: they get no corners or seams of their own down there, just the rule passing
behind them, broken only under the active tab, which is why it reads as merged into the
page below it: the rule turns up into ``╯`` (closing off the flat run from the west,
turning north into the tab's wall), leaves the active tab's width open (no line — that
gap *is* the page starting), then turns back down through ``╰`` (the wall meeting the
flat run continuing east) and carries on flat. A lone tab collapses to the plain accent
heading (:func:`~meshterm.ui.menus.section_heading`'s ``── Label ──`` form) since there
is nothing to switch between or layer. When there's slack in ``width``, the tab boxes
(top and label rows) sit two columns in from the left; the bottom rule is one continuous
line regardless, so it fills that margin rather than being indented with them. The
owning screen switches the active index (``Tab``/``Shift+Tab``); the strip itself is
pure presentation.
Args:
labels: The tab names in display order.
@@ -960,35 +964,33 @@ def tab_strip(labels: Sequence[str], active: int, width: int) -> Group:
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 "muted"
return "accent" if top_owner(junction) == active else "faint"
def bot_glyph(junction: int) -> tuple[str, str]:
"""The bottom rule's glyph at this junction: an accent step around the active tab,
else a flat muted pass-through — see the docstring's continuous-rule rule."""
def bot_glyph(junction: int) -> str:
"""The bottom rule's glyph at this junction — a corner turning into/out of the
active tab's wall, else a flat pass-through. Always accent: see the docstring."""
if junction == active:
return "╰", "accent"
return "╯"
if junction == active + 1:
return "╯", "accent"
return "─", "muted"
return "╰"
return "─"
top = Text(pad, no_wrap=True)
mid = Text(pad, no_wrap=True)
bot = Text(pad, no_wrap=True)
bot = Text("─" * margin, style="accent", 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))
glyph, style = bot_glyph(i)
bot.append(glyph, style=style)
tab_style = "accent" if i == active else "muted"
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=tab_style)
bot.append(" " * len(inner[i]) if i == active else "─" * len(inner[i]), style="accent")
top.append("╮", style=top_style(n))
mid.append("│", style=top_style(n))
glyph, style = bot_glyph(n)
bot.append(glyph, style=style)
bot.append("─" * max(0, width - len(bot.plain)), style="muted")
bot.append(bot_glyph(n), style="accent")
bot.append("─" * max(0, width - len(bot.plain)), style="accent")
return Group(top, mid, bot)
+24 -9
View File
@@ -136,13 +136,27 @@ def test_tab_strip_boxes_every_tab_on_top_and_lights_the_active_one() -> None:
def test_tab_strip_bottom_is_a_continuous_rule_notched_at_the_active_tab() -> None:
"""The bottom border is flat under inactive tabs and opens only under the active one."""
group = tab_strip(["Map", "Routes"], 1, width=20)
_, _, bot = group.renderables
# flat rule under "Map" (no corner of its own), then the notch opens/closes around
# "Routes" — up into the box, blank across its width (open into the page), back down.
assert bot.plain == "────────╰ ╯"
top, mid, bot = group.renderables
# flat rule under "Map" (no corner of its own), then the notch turns up (west+north,
# ╯) into "Routes", blank across its width (open into the page), back down (north+east,
# ╰) and on.
assert bot.plain == "────────╯ ╰"
# One continuous line: every cell reads accent, the active tab's colour, not a mix.
assert {r.style for r in bot.spans} == {"accent"}
_, _, bot_first = tab_strip(["Map", "Routes"], 0, width=20).renderables
assert bot_first.plain == "╰ ╯───────────" # notch now sits under the first tab
assert bot_first.plain == "╯ ╰───────────" # notch now sits under the first tab
def test_tab_strip_unselected_tabs_read_darker_than_muted() -> None:
"""Inactive tab outlines use the dim ``faint`` shade, not the everyday ``muted`` one."""
top, mid, _ = tab_strip(["Map", "Routes"], 1, width=20).renderables
# "Map"'s own left corner and fill (columns 0-7); its right corner at column 8 is
# shared with the active "Routes" tab and reads accent, so it's excluded here.
map_styles = {s.style for s in top.spans if s.start < 8} | {
s.style for s in mid.spans if s.start < 8
}
assert map_styles == {"faint"}
def test_tab_strip_labels_hold_position_across_the_active_index() -> None:
@@ -155,10 +169,11 @@ def test_tab_strip_labels_hold_position_across_the_active_index() -> None:
def test_tab_strip_rule_fills_out_to_the_render_width() -> None:
"""The bottom rule extends to the full render width, margin included."""
_, _, bot = tab_strip(["Map", "Routes"], 1, width=25).renderables
# 2-column left margin (there's room), then the 20-cell strip, then 3 cells of filler.
assert bot.plain == " ────────╰ ╯───"
"""The bottom rule extends to the full render width; unlike the tabs, it isn't indented
by the left margin — that margin is itself part of the one continuous line."""
top, _, bot = tab_strip(["Map", "Routes"], 1, width=25).renderables
assert top.plain.startswith(" ╭") # the tab boxes sit indented...
assert bot.plain == "──────────╯ ╰───" # ...but the rule fills straight through
assert len(bot.plain) == 25