chore(core): hide empty "info" button on Bolt `confirm_with_info()`
What changed, and why it matters
This is a small user-interface cleanup for the Trezor hardware wallet. On one device style (Bolt), it hides an extra 'info' button when there is no label text for it, so users don't see a confusing blank button. The change also adjusts internal Rust return types so the different device layouts can return the same kind of object. There is no direct security fix here.
No security action required. Treat as a normal UI polish commit. If reviewing for release notes, note it as a minor UX improvement.
Security signals we found
No security-relevant signal in the diff: no memory-safety fixes, no input validation changes, no cryptographic changes, no privilege changes.
The change is purely UI/UX: hiding an empty button label.
Return-type unification is an internal refactor to accommodate two possible button configurations in Bolt.
Evidence from the diff
The commit changes confirm_with_info() across four Rust UI layout implementations (Bolt, Caesar, Delizia, Eckhart) to return Gc<LayoutObj> instead of impl LayoutMaybeTrace, and updates the trait and Micropython binding accordingly. The only behavioral change is in the Bolt layout: when verb_info is empty, it uses Button::cancel_confirm_text() instead of Button::cancel_info_confirm(), effectively hiding the info button. A Python caller in core/src/trezor/ui/layouts/bolt/__init__.py is also adjusted so it passes an explicit info label only when button_text is provided, otherwise falling back to the translated ‘show all’ string.
Changed components
Trezor Core firmware UI layerBolt layout implementation of confirm_with_info()Caesar/Delizia/Eckhart layout implementations (type signature only)Micropython firmware UI binding (`new_confirm_with_info`)Python Bolt layout helper `should_show_more()`Inspect captured patch +28 / −21
diff --git a/core/embed/rust/src/ui/api/firmware_micropython.rs b/core/embed/rust/src/ui/api/firmware_micropython.rs
index f62af4f9..603fdf4d 100644
--- a/core/embed/rust/src/ui/api/firmware_micropython.rs
+++ b/core/embed/rust/src/ui/api/firmware_micropython.rs
@@ -476,7 +476,7 @@ extern "C" fn new_confirm_with_info(n_args: usize, args: *const Obj, kwargs: *mu
.try_into_option()?;
let external_menu: bool = kwargs.get_or(Qstr::MP_QSTR_external_menu, false)?;
- let layout = ModelUI::confirm_with_info(
+ let obj = ModelUI::confirm_with_info(
title,
subtitle,
items,
@@ -485,7 +485,7 @@ extern "C" fn new_confirm_with_info(n_args: usize, args: *const Obj, kwargs: *mu
verb_cancel,
external_menu,
)?;
- Ok(LayoutObj::new_root(layout)?.into())
+ Ok(obj.into())
};
unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) }
}
diff --git a/core/embed/rust/src/ui/layout_bolt/ui_firmware.rs b/core/embed/rust/src/ui/layout_bolt/ui_firmware.rs
index 84588398..f6728807 100644
--- a/core/embed/rust/src/ui/layout_bolt/ui_firmware.rs
+++ b/core/embed/rust/src/ui/layout_bolt/ui_firmware.rs
@@ -495,7 +495,7 @@ impl FirmwareUI for UIBolt {
verb_info: TString<'static>,
_verb_cancel: Option<TString<'static>>,
_external_menu: bool,
- ) -> Result<impl LayoutMaybeTrace, Error> {
+ ) -> Result<Gc<LayoutObj>, Error> {
let mut paragraphs = ParagraphVecShort::new();
for para in IterBuf::new().try_iterate(items)? {
@@ -520,14 +520,22 @@ impl FirmwareUI for UIBolt {
}
.styled(theme::button_confirm());
- let buttons = Button::cancel_info_confirm(confirm_button, verb_info);
-
- let layout = RootComponent::new(Frame::left_aligned(
- theme::label_title(),
- title,
- Dialog::new(paragraphs.into_paragraphs(), buttons),
- ));
- Ok(layout)
+ if verb_info.is_empty() {
+ // hide the info button if its verb is empty
+ let buttons = Button::cancel_confirm_text(None, Some(verb));
+ LayoutObj::new(Frame::left_aligned(
+ theme::label_title(),
+ title,
+ Dialog::new(paragraphs.into_paragraphs(), buttons),
+ ))
+ } else {
+ let buttons = Button::cancel_info_confirm(confirm_button, verb_info);
+ LayoutObj::new(Frame::left_aligned(
+ theme::label_title(),
+ title,
+ Dialog::new(paragraphs.into_paragraphs(), buttons),
+ ))
+ }
}
fn check_homescreen_format(image: BinaryData, _accept_toif: bool) -> bool {
diff --git a/core/embed/rust/src/ui/layout_caesar/ui_firmware.rs b/core/embed/rust/src/ui/layout_caesar/ui_firmware.rs
index 41489d0f..93f93efe 100644
--- a/core/embed/rust/src/ui/layout_caesar/ui_firmware.rs
+++ b/core/embed/rust/src/ui/layout_caesar/ui_firmware.rs
@@ -626,7 +626,7 @@ impl FirmwareUI for UICaesar {
verb_info: TString<'static>,
verb_cancel: Option<TString<'static>>,
external_menu: bool,
- ) -> Result<impl LayoutMaybeTrace, Error> {
+ ) -> Result<Gc<LayoutObj>, Error> {
let mut paragraphs = ParagraphVecShort::new();
for para in IterBuf::new().try_iterate(items)? {
@@ -644,7 +644,7 @@ impl FirmwareUI for UICaesar {
}
}
- let layout = RootComponent::new(Frame::new(
+ LayoutObj::new(Frame::new(
title,
ShowMore::<Paragraphs<ParagraphVecShort>>::new(
paragraphs.into_paragraphs(),
@@ -653,8 +653,7 @@ impl FirmwareUI for UICaesar {
verb_info,
)
.with_menu(external_menu),
- ));
- Ok(layout)
+ ))
}
fn check_homescreen_format(image: BinaryData, _accept_toif: bool) -> bool {
diff --git a/core/embed/rust/src/ui/layout_delizia/ui_firmware.rs b/core/embed/rust/src/ui/layout_delizia/ui_firmware.rs
index 9ed92edc..391d12e8 100644
--- a/core/embed/rust/src/ui/layout_delizia/ui_firmware.rs
+++ b/core/embed/rust/src/ui/layout_delizia/ui_firmware.rs
@@ -469,7 +469,7 @@ impl FirmwareUI for UIDelizia {
verb_info: TString<'static>,
_verb_cancel: Option<TString<'static>>,
_external_menu: bool,
- ) -> Result<impl LayoutMaybeTrace, Error> {
+ ) -> Result<Gc<LayoutObj>, Error> {
let mut paragraphs = ParagraphVecShort::new();
for para in IterBuf::new().try_iterate(items)? {
@@ -500,7 +500,7 @@ impl FirmwareUI for UIDelizia {
strings,
ConfirmActionOptions::new(),
)?;
- Ok(flow)
+ LayoutObj::new_root(flow)
}
fn check_homescreen_format(image: BinaryData, __accept_toif: bool) -> bool {
diff --git a/core/embed/rust/src/ui/layout_eckhart/ui_firmware.rs b/core/embed/rust/src/ui/layout_eckhart/ui_firmware.rs
index c5c0853e..d76104bd 100644
--- a/core/embed/rust/src/ui/layout_eckhart/ui_firmware.rs
+++ b/core/embed/rust/src/ui/layout_eckhart/ui_firmware.rs
@@ -580,7 +580,7 @@ impl FirmwareUI for UIEckhart {
verb_info: TString<'static>,
_verb_cancel: Option<TString<'static>>,
_external_menu: bool,
- ) -> Result<impl LayoutMaybeTrace, Error> {
+ ) -> Result<Gc<LayoutObj>, Error> {
let mut paragraphs = ParagraphVecShort::new();
for para in IterBuf::new().try_iterate(items)? {
@@ -611,7 +611,7 @@ impl FirmwareUI for UIEckhart {
Some(verb_info),
None,
)?;
- Ok(flow)
+ LayoutObj::new_root(flow)
}
fn check_homescreen_format(image: BinaryData, _accept_toif: bool) -> bool {
diff --git a/core/embed/rust/src/ui/ui_firmware.rs b/core/embed/rust/src/ui/ui_firmware.rs
index 34fb11f6..b2da63fa 100644
--- a/core/embed/rust/src/ui/ui_firmware.rs
+++ b/core/embed/rust/src/ui/ui_firmware.rs
@@ -169,7 +169,7 @@ pub trait FirmwareUI {
verb_info: TString<'static>,
verb_cancel: Option<TString<'static>>,
external_menu: bool,
- ) -> Result<impl LayoutMaybeTrace, Error>;
+ ) -> Result<Gc<LayoutObj>, Error>;
fn continue_recovery_homepage(
text: TString<'static>,
diff --git a/core/src/trezor/ui/layouts/bolt/__init__.py b/core/src/trezor/ui/layouts/bolt/__init__.py
index 387d0103..c7cb9c8f 100644
--- a/core/src/trezor/ui/layouts/bolt/__init__.py
+++ b/core/src/trezor/ui/layouts/bolt/__init__.py
@@ -649,7 +649,7 @@ async def should_show_more(
title=title,
items=items,
verb=confirm or TR.buttons__confirm,
- verb_info=button_text or TR.buttons__show_all,
+ verb_info=TR.buttons__show_all if button_text is None else button_text,
),
br_name,
br_code,
Why this scored 19/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.