diff --git a/src/element_style.py b/src/element_style.py index 5319ee07..6e4a30ae 100644 --- a/src/element_style.py +++ b/src/element_style.py @@ -763,6 +763,12 @@ def _adopt_handwritten_block(schema: Dict[str, Any], for key in element_keys: _upgrade_font_property(props[key]) + # Marked like a declared element, so consumers can tell the style + # blocks from whatever else the plugin keeps under customization. + # Football's block also holds favorite_result_colors, which is a + # feature with its own fields -- without this the editor treats it + # as an element and every row grows an "enabled"/"win color" column. + props[key]['x-style-managed'] = True modes = customization.get('x-style-modes') if isinstance(modes, list) and modes: @@ -904,11 +910,14 @@ def _normalize_color(value: Any) -> Optional[Tuple[int, int, int]]: return None if isinstance(value, (list, tuple)) and len(value) == 3: try: - rgb = tuple(int(c) for c in value) + # Clamped, not rejected. The readers this replaced clamped + # (sports_card.coerce_rgb), and the eight scoreboards' own tests + # pin it: a configured [999, -5, 20] is a typo'd bright red, and + # answering "unusable, take the default" turned it white instead. + rgb = tuple(max(0, min(255, int(c))) for c in value) except (TypeError, ValueError): return None - if all(0 <= c <= 255 for c in rgb): - return rgb # type: ignore[return-value] + return rgb # type: ignore[return-value] return None diff --git a/test/test_sports_card.py b/test/test_sports_card.py index 0bf88f37..f8de86f5 100644 --- a/test/test_sports_card.py +++ b/test/test_sports_card.py @@ -53,6 +53,14 @@ class TestColour: cfg = {"customization": {"score_text": {"text_color": value}}} assert C.element_color(cfg, "score_text", (9, 9, 9)) == (9, 9, 9) + def test_out_of_range_components_are_clamped_not_rejected(self): + """The readers this replaced clamped, and the eight scoreboards' own + tests pin it. Rejecting instead turned a typo'd [999, -5, 20] -- a + bright red -- into the default white, which is a colour the user never + asked for rather than the one they nearly asked for.""" + cfg = {"customization": {"score_text": {"text_color": [999, -5, 20]}}} + assert C.element_color(cfg, "score_text", (9, 9, 9)) == (255, 0, 20) + def test_coerce_rgb_clamps_rather_than_rejecting(self): assert C.coerce_rgb([300, -5, 20], (0, 0, 0)) == (255, 0, 20)