From bf992979effc44a8969a9675d5dfdb21ffff78f1 Mon Sep 17 00:00:00 2001 From: Jean-Pierre Martineau Date: Sun, 26 Jul 2026 10:53:16 -0400 Subject: [PATCH] =?UTF-8?q?Revert=20the=20share=20card=20QR=20experiments?= =?UTF-8?q?=20=E2=80=94=20back=20to=20half-blocks?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Braille (one dot per module) would not scan, and the quadrant mosaic reads worse on screen than the plain half-block form. The original qr_text render scans and stays. Co-Authored-By: Claude Fable 5 --- meshterm/ui/config_editor.py | 4 +-- meshterm/ui/qr.py | 65 ------------------------------------ tests/test_config_editor.py | 3 +- tests/test_qr.py | 41 ----------------------- 4 files changed, 3 insertions(+), 110 deletions(-) delete mode 100644 tests/test_qr.py diff --git a/meshterm/ui/config_editor.py b/meshterm/ui/config_editor.py index f4ba0f5..78723ff 100644 --- a/meshterm/ui/config_editor.py +++ b/meshterm/ui/config_editor.py @@ -815,13 +815,13 @@ async def show_contact_card( node_type: The MeshCore advert type byte (1 companion, 2 repeater, 3 room, 4 sensor). """ - from .qr import qr_quad + from .qr import qr_text url = contact_share_url(name, public_key, node_type) body = Group( Text("Scan to add this node as a contact:", style="muted"), Text(""), - qr_quad(url), + qr_text(url), Text(""), Text(url, style="accent"), ) diff --git a/meshterm/ui/qr.py b/meshterm/ui/qr.py index f66dace..098ef56 100644 --- a/meshterm/ui/qr.py +++ b/meshterm/ui/qr.py @@ -5,13 +5,6 @@ Each character cell stacks two vertical modules — ``▀`` (upper only), ``▄` one and comfortably fits an interactive window. Modules are always drawn black on a white background (with the mandatory quiet-zone border), so a phone camera reads the code no matter what colour theme the terminal itself is using. - -:func:`qr_quad` is the dense variant: quadrant mosaic glyphs (``▘▚▛█``…) pack a 2×2 -block of modules into every cell — half the columns of the half-block form at the same -row count. Ink stays solid (every dark module is a filled quarter-cell), so it scans -exactly as well as the half-block render, just drawn smaller. (A braille one-dot-per- -module form was tried first: it drew even smaller but the dots' white surrounds starve -the scanner of ink, and phones would not read it.) """ from __future__ import annotations @@ -34,28 +27,6 @@ _GLYPH = { #: contrast holds regardless of the surrounding terminal palette. _STYLE = "black on white" -#: Quadrant glyph for each (top-left, top-right, bottom-left, bottom-right) dark-module -#: quartet — the sixteen 2×2 mosaics Unicode provides (space and the half/full blocks -#: fill the patterns the Block Elements quadrant range skips). -_QUAD = { - (False, False, False, False): " ", - (True, False, False, False): "▘", - (False, True, False, False): "▝", - (True, True, False, False): "▀", - (False, False, True, False): "▖", - (True, False, True, False): "▌", - (False, True, True, False): "▞", - (True, True, True, False): "▛", - (False, False, False, True): "▗", - (True, False, False, True): "▚", - (False, True, False, True): "▐", - (True, True, False, True): "▜", - (False, False, True, True): "▄", - (True, False, True, True): "▙", - (False, True, True, True): "▟", - (True, True, True, True): "█", -} - def qr_text(data: str, *, error: str = "m") -> Text: """Render ``data`` as a QR code built from half-block characters. @@ -83,39 +54,3 @@ def qr_text(data: str, *, error: str = "m") -> Text: text.append(line, style=_STYLE) text.append("\n") return text - - -def qr_quad(data: str, *, error: str = "m") -> Text: - """Render ``data`` as a QR code built from quadrant mosaics, 2×2 modules per cell. - - Half the width of :func:`qr_text` at the same row count, and every dark module is - still a solid quarter-cell of ink — the property a scanner actually needs — so the - smaller draw reads as well as the half-block form. - - Args: - data: The string to encode. - error: QR error-correction level — ``l``/``m``/``q``/``h`` (default ``m``). - - Returns: - A Rich :class:`~rich.text.Text` (no-wrap) whose lines draw the code. - """ - import segno - - qr = segno.make(data, error=error) - rows = [[bool(v) for v in row] for row in qr.matrix_iter(border=_BORDER)] - # Pad to whole cells with light modules: out to an even row and column count. - if len(rows) % 2: - rows.append([False] * len(rows[0])) - if len(rows[0]) % 2: - for row in rows: - row.append(False) - - text = Text(no_wrap=True) - for top, bottom in zip(rows[0::2], rows[1::2], strict=True): - line = "".join( - _QUAD[(top[x], top[x + 1], bottom[x], bottom[x + 1])] - for x in range(0, len(top), 2) - ) - text.append(line, style=_STYLE) - text.append("\n") - return text diff --git a/tests/test_config_editor.py b/tests/test_config_editor.py index 397a796..afbed50 100644 --- a/tests/test_config_editor.py +++ b/tests/test_config_editor.py @@ -91,8 +91,7 @@ async def test_show_contact_card_pops_the_qr_over_the_link() -> None: console.print(renderable) out = console.export_text() assert f"meshcore://contact/add?name=Hub&public_key={'ab' * 32}&type=2" in out - # The QR actually drew its modules — quadrant mosaics, 2×2 modules per glyph. - assert "█" in out + assert "█" in out # the QR actually drew its modules # -- coordinate parsing ---------------------------------------------------------- diff --git a/tests/test_qr.py b/tests/test_qr.py deleted file mode 100644 index 14278ff..0000000 --- a/tests/test_qr.py +++ /dev/null @@ -1,41 +0,0 @@ -"""The terminal QR renderers: half-block and quadrant-mosaic forms of the same matrix.""" - -import segno - -from meshterm.ui.qr import _BORDER, _QUAD, qr_quad, qr_text - -URL = "meshcore://contact/add?name=Test&public_key=" + "ab" * 32 + "&type=1" - - -def _matrix(data: str) -> list[list[bool]]: - qr = segno.make(data, error="m") - return [[bool(v) for v in row] for row in qr.matrix_iter(border=_BORDER)] - - -def test_qr_text_matches_matrix() -> None: - rows = _matrix(URL) - lines = qr_text(URL).plain.rstrip("\n").split("\n") - glyph_bits = {"█": (True, True), "▀": (True, False), "▄": (False, True), " ": (False, False)} - for y, row in enumerate(rows): - for x, dark in enumerate(row): - top, bottom = glyph_bits[lines[y // 2][x]] - assert (top if y % 2 == 0 else bottom) == dark - - -def test_qr_quad_matches_matrix() -> None: - """Every module maps to exactly its quarter of a mosaic glyph, raster order.""" - rows = _matrix(URL) - lines = qr_quad(URL).plain.rstrip("\n").split("\n") - quarters = {glyph: quartet for quartet, glyph in _QUAD.items()} - for y, row in enumerate(rows): - for x, dark in enumerate(row): - quartet = quarters[lines[y // 2][x // 2]] - assert quartet[(y % 2) * 2 + (x % 2)] == dark - - -def test_qr_quad_geometry() -> None: - """Whole-cell padding: equal-length lines at half the matrix width and height.""" - rows = _matrix(URL) - lines = qr_quad(URL).plain.rstrip("\n").split("\n") - assert len(lines) == -(-len(rows) // 2) - assert all(len(line) == -(-len(rows[0]) // 2) for line in lines)