What changed, and why it matters
This commit removes a UI flag called allow_text_overflow from SeedSigner's on-screen text rendering code. Previously, the flag decided whether text that was too long should be silently allowed to spill past its box (overflow) or trigger an error. After the change, the code always allows overflow and just logs a warning. This is a code-cleanup and UI-layout change; it does not introduce a way for an attacker to steal funds or keys, but it could make some translated screens look clipped or unreadable.
Treat as a routine UI refactor. Reviewers may want to verify that screens with long translations still render legibly, since overflow is now silently allowed rather than raising an exception. No security patch or incident response is indicated.
Security signals we found
No cryptographic, wallet-seed, or input-validation code is touched
Exception path removed: TextDoesNotFitException no longer raised for single-word overflow
All changes are in GUI rendering/layout components
No network, file-system, or privileged operations are added
No references to CVEs, security advisories, or researchers in commit message or diff
Evidence from the diff
The patch deletes the allow_text_overflow boolean parameter from TextArea, IconTextLine, Button, ScrollableTextLine, reflow_text_for_width, reflow_text_into_pages, and several screen classes. Behavior is unified: text that does not fit is always rendered past the boundary with a logger.warning, instead of raising TextDoesNotFitException in some paths. The only functional change is in reflow_text_for_width, where a single-word string that is too wide now logs a warning instead of raising TextDoesNotFitException. This is a defensive-softening change (fewer crashes, more visual overflow).
Changed components
src/seedsigner/gui/components.pysrc/seedsigner/gui/screens/psbt_screens.pysrc/seedsigner/gui/screens/screen.pysrc/seedsigner/gui/screens/seed_screens.pysrc/seedsigner/gui/toast.pysrc/seedsigner/views/view.pytests/test_flows_seed.pyInspect captured patch +9 / −42
diff --git a/src/seedsigner/gui/components.py b/src/seedsigner/gui/components.py
index d9a2e0b..fbeccd7 100644
--- a/src/seedsigner/gui/components.py
+++ b/src/seedsigner/gui/components.py
@@ -390,7 +390,6 @@ class TextArea(BaseComponent):
is_text_centered: bool = True
supersampling_factor: int = 2 # 1 = disabled; 2 = default, double sample (4px square rendered for 1px)
auto_line_break: bool = True
- allow_text_overflow: bool = False
is_horizontal_scrolling_enabled: bool = False
horizontal_scroll_speed: int = 40 # px per sec
horizontal_scroll_begin_hold_secs: float = 2.0
@@ -402,10 +401,6 @@ class TextArea(BaseComponent):
if self.is_horizontal_scrolling_enabled and self.auto_line_break:
raise Exception("TextArea: Cannot have auto_line_break and horizontal scrolling enabled at the same time")
- if self.is_horizontal_scrolling_enabled and not self.allow_text_overflow:
- self.allow_text_overflow = True
- logger.warning("TextArea: allow_text_overflow gets overridden to True when horizontal scrolling is enabled")
-
if not self.font_name:
self.font_name = GUIConstants.get_body_font_name()
if not self.font_size:
@@ -458,7 +453,6 @@ class TextArea(BaseComponent):
width=self.width - 2*self.edge_padding,
font_name=self.font_name,
font_size=self.font_size,
- allow_text_overflow=self.allow_text_overflow,
)
# Other components, like IconTextLine will need to know how wide the actual
@@ -485,14 +479,9 @@ class TextArea(BaseComponent):
else:
if total_text_height > self.height:
- if not self.allow_text_overflow:
- # For now, early into the l10n rollout, we can't enforce strict
- # conformance here. Too many screens will just break if this is were
- # to raise an exception.
- logger.warning(f"Text cannot fit in target rect with this font/size\n\ttotal_text_height: {total_text_height} | self.height: {self.height}")
- else:
- # Just let it render past the bottom edge
- pass
+ # Let it render past the bottom edge. Will be up to the dev or translator
+ # to review the screenshot and revise the text as needed.
+ logger.warning(f"Text cannot fit in target rect with this font/size\n\ttotal_text_height: {total_text_height} | self.height: {self.height}")
else:
# Vertically center the text's starting point
@@ -741,7 +730,6 @@ class ScrollableTextLine(TextArea):
def __post_init__(self):
self.auto_line_break = False
self.is_horizontal_scrolling_enabled = True
- self.allow_text_overflow = True
super().__post_init__()
@@ -803,7 +791,6 @@ class IconTextLine(BaseComponent):
font_size: int = None
is_text_centered: bool = False
auto_line_break: bool = False
- allow_text_overflow: bool = True
screen_x: int = 0
screen_y: int = 0
@@ -846,7 +833,6 @@ class IconTextLine(BaseComponent):
auto_line_break=False,
screen_x=text_screen_x,
screen_y=self.screen_y,
- allow_text_overflow=False,
)
else:
self.label_textarea = None
@@ -866,7 +852,6 @@ class IconTextLine(BaseComponent):
edge_padding=0,
is_text_centered=self.is_text_centered if not self.icon_name else False,
auto_line_break=self.auto_line_break,
- allow_text_overflow=self.allow_text_overflow,
screen_x=text_screen_x,
screen_y=value_textarea_screen_y,
)
@@ -1516,7 +1501,6 @@ class Button(BaseComponent):
button_kwargs["text"] = self.text
button_kwargs["font_color"] = self.font_color
button_kwargs["background_color"] = self.background_color
- button_kwargs["allow_text_overflow"] = True
button_kwargs["auto_line_break"] = False
del button_kwargs["horizontal_scroll_begin_hold_secs"]
del button_kwargs["horizontal_scroll_end_hold_secs"]
@@ -1821,8 +1805,7 @@ def calc_bezier_curve(p1: Tuple[int,int], p2: Tuple[int,int], p3: Tuple[int,int]
def reflow_text_for_width(text: str,
width: int,
font_name=GUIConstants.get_body_font_name(),
- font_size=GUIConstants.get_body_font_size(),
- allow_text_overflow: bool=False) -> list[dict]:
+ font_size=GUIConstants.get_body_font_size()) -> list[dict]:
"""
Reflows text to fit within `width` by breaking long lines up.
@@ -1845,9 +1828,6 @@ def reflow_text_for_width(text: str,
SettingsConstants.LOCALE__JAPANESE,
SettingsConstants.LOCALE__KOREAN,
]
- if treat_chars_as_words:
- # Relax UI constraints even if the result isn't optimal
- allow_text_overflow = True
# Stores each line of text and its rendering starting x-coord
text_lines = []
@@ -1891,9 +1871,9 @@ def reflow_text_for_width(text: str,
# Candidate line is possibly shorter than necessary.
return _binary_len_search(min_index=index, max_index=max_index, word_spacer=word_spacer)
- if len(text.split()) == 1 and not allow_text_overflow and not treat_chars_as_words:
- # No whitespace chars to split on!
- raise TextDoesNotFitException("Text cannot fit in target rect with this font+size")
+ if len(text.split()) == 1 and not treat_chars_as_words:
+ # No whitespace chars to split on! Warn but proceed anyway.
+ logger.warning("Text cannot fit in target rect with this font+size")
# Now we're ready to go line-by-line into our line break binary search!
for line in text.split("\n"):
@@ -1933,8 +1913,7 @@ def reflow_text_into_pages(text: str,
height: int,
font_name=GUIConstants.get_body_font_name(),
font_size=GUIConstants.get_body_font_size(),
- line_spacer: int = GUIConstants.BODY_LINE_SPACING,
- allow_text_overflow: bool=False) -> list[str]:
+ line_spacer: int = GUIConstants.BODY_LINE_SPACING) -> list[str]:
"""
Invokes `reflow_text_for_width` above to convert long text into width-limited
individual text lines and then calculates how many lines will fit on a "page" and
@@ -1945,8 +1924,7 @@ def reflow_text_into_pages(text: str,
reflowed_lines_dicts = reflow_text_for_width(text=text,
width=width,
font_name=font_name,
- font_size=font_size,
- allow_text_overflow=allow_text_overflow)
+ font_size=font_size)
lines = []
for line_dict in reflowed_lines_dicts:
diff --git a/src/seedsigner/gui/screens/psbt_screens.py b/src/seedsigner/gui/screens/psbt_screens.py
index 4a65555..a2b8af0 100644
--- a/src/seedsigner/gui/screens/psbt_screens.py
+++ b/src/seedsigner/gui/screens/psbt_screens.py
@@ -722,7 +722,6 @@ class PSBTOpReturnScreen(ButtonListScreen):
text=self.op_return_data.decode(errors="strict"), # "strict" is a good enough heuristic to decide if it's human readable
font_size=GUIConstants.get_top_nav_title_font_size(),
is_text_centered=True,
- allow_text_overflow=True,
screen_y=self.top_nav.height + GUIConstants.COMPONENT_PADDING,
height=self.buttons[0].screen_y - self.top_nav.height - 2*GUIConstants.COMPONENT_PADDING,
))
diff --git a/src/seedsigner/gui/screens/screen.py b/src/seedsigner/gui/screens/screen.py
index 0141929..20e3379 100644
--- a/src/seedsigner/gui/screens/screen.py
+++ b/src/seedsigner/gui/screens/screen.py
@@ -804,7 +804,6 @@ class QRDisplayScreen(BaseScreen):
width=int(rectangle_width/2),
screen_x=chevron_up_icon.screen_x + GUIConstants.ICON_INLINE_FONT_SIZE,
screen_y=chevron_up_icon.screen_y - 2, # -2 to account for Icon's positioning
- allow_text_overflow=False
).render()
# TRANSLATOR_NOTE: Decrease QR code screen brightness
@@ -822,7 +821,6 @@ class QRDisplayScreen(BaseScreen):
width=int(rectangle_width/2),
screen_x=chevron_down_icon.screen_x + GUIConstants.ICON_INLINE_FONT_SIZE,
screen_y=chevron_down_icon.screen_y - 2, # -2 to account for Icon's positioning
- allow_text_overflow=False
).render()
# Write our temp Image onto the main image
@@ -924,7 +922,6 @@ class LargeIconStatusScreen(ButtonListScreen):
text: str = "" # The body text of the screen
text_edge_padding: int = GUIConstants.EDGE_PADDING
button_data: list = None
- allow_text_overflow: bool = False
def __post_init__(self):
diff --git a/src/seedsigner/gui/screens/seed_screens.py b/src/seedsigner/gui/screens/seed_screens.py
index ba684f9..8862b5f 100644
--- a/src/seedsigner/gui/screens/seed_screens.py
+++ b/src/seedsigner/gui/screens/seed_screens.py
@@ -1129,7 +1129,6 @@ class SeedReviewPassphraseScreen(ButtonListScreen):
font_color="orange",
is_text_centered=True,
screen_y=screen_y,
- allow_text_overflow=True
))
screen_y += char_height + 2
@@ -1641,7 +1640,6 @@ class SeedSignMessageConfirmMessageScreen(ButtonListScreen):
text=self.sign_message_data["message"],
width=renderer.canvas_width - 2*GUIConstants.EDGE_PADDING,
height=message_height,
- allow_text_overflow=True,
)
self.sign_message_data["paged_message"] = paged
@@ -1660,7 +1658,6 @@ class SeedSignMessageConfirmMessageScreen(ButtonListScreen):
message_display = TextArea(
text=self.sign_message_data["paged_message"][self.page_num],
is_text_centered=False,
- allow_text_overflow=True,
screen_y=start_y,
)
self.components.append(message_display)
diff --git a/src/seedsigner/gui/toast.py b/src/seedsigner/gui/toast.py
index 2f7a406..b02fdd5 100644
--- a/src/seedsigner/gui/toast.py
+++ b/src/seedsigner/gui/toast.py
@@ -45,7 +45,6 @@ class ToastOverlay(BaseComponent):
auto_line_break=True,
width=self.canvas_width - icon_delta_x - 2 * GUIConstants.COMPONENT_PADDING - 2 * self.outline_thickness,
screen_x=icon_delta_x + GUIConstants.COMPONENT_PADDING,
- allow_text_overflow=False,
height_ignores_below_baseline=True,
)
diff --git a/src/seedsigner/views/view.py b/src/seedsigner/views/view.py
index bd51174..08c5693 100644
--- a/src/seedsigner/views/view.py
+++ b/src/seedsigner/views/view.py
@@ -382,7 +382,6 @@ class UnhandledExceptionView(View):
status_headline=self.error[0],
text=self.error[1] + "\n" + self.error[2],
button_data=[ButtonOption("Back to Main Menu")],
- allow_text_overflow=True, # Fit what we can, let the rest go off the edges
)
return Destination(MainMenuView, clear_history=True)
@@ -429,7 +428,6 @@ class OptionDisabledView(View):
text=self.error_msg,
button_data=button_data,
show_back_button=False,
- allow_text_overflow=True, # Fit what we can, let the rest go off the edges
)
if button_data[selected_menu_num] == self.UPDATE_SETTING:
diff --git a/tests/test_flows_seed.py b/tests/test_flows_seed.py
index 2923156..2b2339f 100644
--- a/tests/test_flows_seed.py
+++ b/tests/test_flows_seed.py
@@ -528,7 +528,6 @@ class TestMessageSigningFlows(FlowTest):
text=self.controller.sign_message_data["message"],
width=240 - 2*GUIConstants.EDGE_PADDING,
height=240 - GUIConstants.TOP_NAV_HEIGHT - 3*GUIConstants.EDGE_PADDING - GUIConstants.BUTTON_HEIGHT,
- allow_text_overflow=True,
)
self.controller.sign_message_data["paged_message"] = paged
Why this scored 17/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.