From dda0a38ed05e769b2406c77f073f1bedfb57f311 Mon Sep 17 00:00:00 2001 From: Jean-Pierre Martineau Date: Sun, 6 Sep 2026 12:10:26 -0400 Subject: [PATCH] The cooldown countdown centers its clock, reason, and Cancel chip MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The wait dialog drew its three lines flush left while every other dialog in the app centers its body — so the Cancel chip sat against the border, reading as an afterthought beside the button rows it is a sibling of. It now centers line by line the way ButtonDialog and ProgressDialog do, and takes the same +12 breathing room in its content-sized width (49 cells at its widest, still inside the PicoCalc's 53). Co-Authored-By: Claude Opus 5 (1M context) --- meshterm/ui/tui/prompt.py | 26 ++++++++++++++------------ tests/test_cooldown.py | 12 ++++++++++++ 2 files changed, 26 insertions(+), 12 deletions(-) diff --git a/meshterm/ui/tui/prompt.py b/meshterm/ui/tui/prompt.py index 3bce656..389819f 100644 --- a/meshterm/ui/tui/prompt.py +++ b/meshterm/ui/tui/prompt.py @@ -662,7 +662,7 @@ class CountdownDialog(_KeylessDialog): cell_len(self._reason), len(" Cancel "), ) - return inner + 8 + return inner + 12 # panel padding + border, plus breathing room (see ButtonDialog) @staticmethod def _clock_text(remaining: float) -> str: @@ -671,18 +671,20 @@ class CountdownDialog(_KeylessDialog): return f"Ready in {seconds}s" def render_body(self, width: int) -> list[str]: - """Draw the clock, its reason, and the Cancel chip.""" - lines: list[Text] = [Text(self._clock_text(self._remaining), style="warn")] + """Draw the clock and its reason centered above a centered Cancel chip. + + Centered line by line, the way :class:`ButtonDialog` and :class:`ProgressDialog` + draw theirs: a chip is a chip wherever it appears, and one shoved against the left + border read as an afterthought beside its siblings' centered rows. + """ + parts: list[Text] = [ + _center(Text(self._clock_text(self._remaining), style="warn"), width) + ] if self._reason: - lines.append(Text(self._reason, style="muted")) - lines.append(Text("")) - lines.append(Text(" Cancel ", style="selected")) - rendered: list[str] = [] - for line in lines: - # An empty Text renders to no lines at all on its own, so the spacer above the - # chip has to be asked for explicitly or the chip rides up against the reason. - rendered.extend(render_lines(line, width) or [""]) - return rendered + parts.append(_center(Text(self._reason, style="muted"), width)) + parts.append(Text("")) + parts.append(_center(Text(" Cancel ", style="selected"), width)) + return render_lines(Group(*parts), width) def handle(self, action: str, data: str = "") -> None: """Enter and Esc both abandon: the only chip on the dialog is Cancel.""" diff --git a/tests/test_cooldown.py b/tests/test_cooldown.py index 5402743..867dc45 100644 --- a/tests/test_cooldown.py +++ b/tests/test_cooldown.py @@ -110,6 +110,18 @@ def test_the_countdown_shows_the_clock_its_reason_and_the_way_out() -> None: assert dialog.footer_hint == "Enter/Esc cancel" +def test_the_clock_its_reason_and_the_chip_are_all_centered() -> None: + """Centered like every other dialog's body — a chip against the left border read as odd.""" + dialog = CountdownDialog("Flood advert", 47.0, reason="reaches the whole mesh") + width = dialog.dialog_width - 8 + for line in _lines(dialog): + if not line.strip(): + continue + left = len(line) - len(line.lstrip(" ")) + right = width - len(line.rstrip(" ")) + assert abs(left - right) <= 1, line + + def test_a_part_second_still_reads_as_a_second() -> None: """The clock counts the way a person would say it: 0.2s left is still "1s".""" assert "Ready in 1s" in _lines(CountdownDialog("Advert", 0.2))[0]