diff --git a/docs/developer/gui.md b/docs/developer/gui.md index 413ea47..5bf16da 100644 --- a/docs/developer/gui.md +++ b/docs/developer/gui.md @@ -54,8 +54,9 @@ The entry widget has three internal modes: - **Read-back:** starts empty, normalizes only spacing and ASCII case, and never reveals expected text after a mismatch. -Enter activates the enabled primary action. Card displays use four -four-character groups per row, matching `docs/user/recovery-card.html`. +Enter activates the enabled primary action. Card displays use uppercase +four-character groups in reading order, on one line when space permits and +wrapping when needed; the paper layout remains in `docs/user/recovery-card.html`. Ordinary repair suggestions are unmarked; explicit correction may mark changed groups, which are described as changes rather than known error locations. diff --git a/tools/gui_walkthrough.py b/tools/gui_walkthrough.py index 8c68ba9..4826fb2 100644 --- a/tools/gui_walkthrough.py +++ b/tools/gui_walkthrough.py @@ -13,6 +13,7 @@ from __future__ import annotations from collections.abc import Callable, Iterator +from itertools import pairwise from typing import Any import gi @@ -294,38 +295,35 @@ def short_repair_candidate(self) -> bool: groups = [item for item in walk(page) if "card-group" in item.get_css_classes()] flow = next(item for item in walk(page) if isinstance(item, Gtk.FlowBox)) - def row_positions() -> list[float]: + def group_positions() -> list[tuple[float, float]]: children = [flow.get_child_at_index(index) for index in range(len(groups))] - return [child.compute_bounds(flow)[1].origin.y for child in children] - - def four_columns(rows_y: list[float]) -> bool: - return len(set(rows_y)) == (len(groups) + 3) // 4 and all( - rows_y[index] == rows_y[(index // 4) * 4] for index in range(len(rows_y)) + positions = [] + for child in children: + valid, bounds = child.compute_bounds(flow) + if not valid: + return [] + positions.append((bounds.origin.x, bounds.origin.y)) + return positions + + def aligned_rows(positions: list[tuple[float, float]]) -> bool: + return len(positions) == len(groups) and all( + next_y > this_y or (next_y == this_y and next_x > this_x) + for (this_x, this_y), (next_x, next_y) in pairwise(positions) ) - rows_y = row_positions() + positions = group_positions() check("Enter invokes repair for short input", "".join(item.get_label() for item in groups) == SHARE_C) check( "ordinary repair does not claim where the error was", not any("guessed" in item.get_css_classes() for item in groups), ) check( - "card display uses four aligned groups per row", - flow.get_min_children_per_line() == 4 - and flow.get_max_children_per_line() == 4 - and four_columns(rows_y), - rows_y, - ) - window = self.get_active_window() - window.set_default_size(640, 620) - settle() - narrow = row_positions() - window.set_default_size(1100, 620) - settle() - wide = row_positions() - check( - "card columns stay aligned when the window is resized", - four_columns(narrow) and four_columns(wide), + "card display wraps groups in reading order", + flow.get_min_children_per_line() == 1 + and flow.get_max_children_per_line() == len(groups) + and aligned_rows(positions) + and len({y for _, y in positions}) > 1, + positions, ) press(page, "It does not match my card") settle()