refactor(eckhart): mark cancel menu buttons
What changed, and why it matters
This is a small internal code cleanup for the user interface of a new Trezor device layout (codenamed 'Eckhart'). It adds a marker flag called is_cancel to buttons that represent a 'Cancel' action, so automated testing/debug tools can identify them more easily. The visible behavior of the device does not change.
No security action required. Treat as a normal UI refactor.
Security signals we found
No strong security signals were identified.
Evidence from the diff
The commit refactors cancel-button construction across multiple Rust UI flows for the layout_eckhart design. A new Button::new_cancel_menu_item() constructor is introduced, which applies the orange cancel style and sets an internal is_cancel boolean. The is_cancel field is also exposed in the debug/trace output. There is no functional change to button behavior, event handling, or security logic.
Changed components
core/embed/rust/src/ui/layout_eckhart/component/button.rscore/embed/rust/src/ui/layout_eckhart/flow/confirm_fido.rscore/embed/rust/src/ui/layout_eckhart/flow/confirm_firmware_update.rscore/embed/rust/src/ui/layout_eckhart/flow/confirm_output.rscore/embed/rust/src/ui/layout_eckhart/flow/confirm_reset.rscore/embed/rust/src/ui/layout_eckhart/flow/confirm_set_new_code.rscore/embed/rust/src/ui/layout_eckhart/flow/confirm_summary.rscore/embed/rust/src/ui/layout_eckhart/flow/confirm_value_intro.rscore/embed/rust/src/ui/layout_eckhart/flow/confirm_with_menu.rscore/embed/rust/src/ui/layout_eckhart/flow/continue_recovery_homepage.rscore/embed/rust/src/ui/layout_eckhart/flow/receive.rscore/embed/rust/src/ui/layout_eckhart/flow/request_number.rscore/embed/rust/src/ui/layout_eckhart/flow/show_thp_pairing_code.rsInspect captured patch +45 / −56
diff --git a/core/embed/rust/src/ui/layout_eckhart/component/button.rs b/core/embed/rust/src/ui/layout_eckhart/component/button.rs
index 700eef06..62e2fa8b 100644
--- a/core/embed/rust/src/ui/layout_eckhart/component/button.rs
+++ b/core/embed/rust/src/ui/layout_eckhart/component/button.rs
@@ -45,8 +45,8 @@ pub struct Button {
long_press_danger: bool,
long_timer: Timer,
haptic: bool,
-
subtext_marquee: Option<Marquee>,
+ is_cancel: bool, // used by debuglink
}
impl Button {
@@ -88,6 +88,7 @@ impl Button {
long_timer: Timer::new(),
haptic: true,
subtext_marquee,
+ is_cancel: false,
}
}
@@ -99,6 +100,16 @@ impl Button {
.with_radius(Self::MENU_ITEM_RADIUS)
}
+ #[cfg(feature = "micropython")]
+ pub fn new_cancel_menu_item(text: TString<'static>) -> Self {
+ Self::with_text(text)
+ .with_text_align(Self::MENU_ITEM_ALIGNMENT)
+ .with_content_offset(Self::MENU_ITEM_CONTENT_OFFSET)
+ .styled(theme::firmware::menu_item_title_orange())
+ .with_radius(Self::MENU_ITEM_RADIUS)
+ .set_is_cancel()
+ }
+
pub fn new_single_line_menu_item(text: TString<'static>, stylesheet: ButtonStyleSheet) -> Self {
Self::with_single_line_text(text)
.with_text_align(Self::MENU_ITEM_ALIGNMENT)
@@ -265,6 +276,11 @@ impl Button {
matches!(self.radius_or_gradient, RadiusOrGradient::Gradient(_))
}
+ pub fn set_is_cancel(mut self) -> Self {
+ self.is_cancel = true;
+ self
+ }
+
pub fn enable_if(&mut self, ctx: &mut EventCtx, enabled: bool) {
if enabled {
self.enable(ctx);
@@ -784,6 +800,7 @@ impl Component for Button {
impl crate::trace::Trace for Button {
fn trace(&self, t: &mut dyn crate::trace::Tracer) {
t.component("Button");
+ t.bool("is_cancel", self.is_cancel);
match &self.content {
ButtonContent::Empty => {}
ButtonContent::Text { text, .. } => t.string("text", *text),
diff --git a/core/embed/rust/src/ui/layout_eckhart/flow/confirm_fido.rs b/core/embed/rust/src/ui/layout_eckhart/flow/confirm_fido.rs
index 48ebe334..59ebbca7 100644
--- a/core/embed/rust/src/ui/layout_eckhart/flow/confirm_fido.rs
+++ b/core/embed/rust/src/ui/layout_eckhart/flow/confirm_fido.rs
@@ -163,9 +163,10 @@ pub fn new_confirm_fido(
});
// Menu screen
- let content_menu = VerticalMenuScreen::new(VerticalMenu::<ShortMenuVec>::empty().with_item(
- Button::new_menu_item(TR::buttons__cancel.into(), theme::menu_item_title_orange()),
- ))
+ let content_menu = VerticalMenuScreen::new(
+ VerticalMenu::<ShortMenuVec>::empty()
+ .with_item(Button::new_cancel_menu_item(TR::buttons__cancel.into())),
+ )
.with_header(Header::new(title).with_close_button())
.map(|msg| match msg {
VerticalMenuScreenMsg::Selected(0) => Some(FlowMsg::Choice(0)),
diff --git a/core/embed/rust/src/ui/layout_eckhart/flow/confirm_firmware_update.rs b/core/embed/rust/src/ui/layout_eckhart/flow/confirm_firmware_update.rs
index c66131ec..946724f8 100644
--- a/core/embed/rust/src/ui/layout_eckhart/flow/confirm_firmware_update.rs
+++ b/core/embed/rust/src/ui/layout_eckhart/flow/confirm_firmware_update.rs
@@ -82,10 +82,7 @@ pub fn new_confirm_firmware_update(
TR::firmware_update__title_fingerprint.into(),
theme::firmware::menu_item_title(),
))
- .with_item(Button::new_menu_item(
- TR::buttons__cancel.into(),
- theme::menu_item_title_orange(),
- ));
+ .with_item(Button::new_cancel_menu_item(TR::buttons__cancel.into()));
let content_menu = VerticalMenuScreen::new(menu)
.with_header(Header::new(title).with_close_button())
diff --git a/core/embed/rust/src/ui/layout_eckhart/flow/confirm_output.rs b/core/embed/rust/src/ui/layout_eckhart/flow/confirm_output.rs
index dbcdcd28..d28729d3 100644
--- a/core/embed/rust/src/ui/layout_eckhart/flow/confirm_output.rs
+++ b/core/embed/rust/src/ui/layout_eckhart/flow/confirm_output.rs
@@ -234,10 +234,7 @@ fn content_main_menu(
));
unwrap!(main_menu_items.push(MENU_ITEM_ACCOUNT_INFO));
}
- main_menu.item(Button::new_menu_item(
- cancel_menu_label,
- theme::menu_item_title_orange(),
- ));
+ main_menu.item(Button::new_cancel_menu_item(cancel_menu_label));
unwrap!(main_menu_items.push(MENU_ITEM_CANCEL));
VerticalMenuScreen::<ShortMenuVec>::new(main_menu)
@@ -427,10 +424,7 @@ pub fn new_confirm_output(
));
unwrap!(summary_menu_items.push(MENU_ITEM_FEE_INFO));
}
- summary_menu.item(Button::new_menu_item(
- cancel_menu_label,
- theme::menu_item_title_orange(),
- ));
+ summary_menu.item(Button::new_cancel_menu_item(cancel_menu_label));
unwrap!(summary_menu_items.push(MENU_ITEM_CANCEL));
let content_summary_menu = VerticalMenuScreen::new(summary_menu)
.with_header(Header::new(TString::empty()).with_close_button())
diff --git a/core/embed/rust/src/ui/layout_eckhart/flow/confirm_reset.rs b/core/embed/rust/src/ui/layout_eckhart/flow/confirm_reset.rs
index 10ec7309..0ee8da83 100644
--- a/core/embed/rust/src/ui/layout_eckhart/flow/confirm_reset.rs
+++ b/core/embed/rust/src/ui/layout_eckhart/flow/confirm_reset.rs
@@ -87,9 +87,10 @@ pub fn new_confirm_reset(recovery: bool) -> Result<SwipeFlow, error::Error> {
})
.one_button_request(br);
- let content_menu = VerticalMenuScreen::new(VerticalMenu::<ShortMenuVec>::empty().with_item(
- Button::new_menu_item(TR::buttons__cancel.into(), theme::menu_item_title_orange()),
- ))
+ let content_menu = VerticalMenuScreen::new(
+ VerticalMenu::<ShortMenuVec>::empty()
+ .with_item(Button::new_cancel_menu_item(TR::buttons__cancel.into())),
+ )
.with_header(Header::new(title).with_close_button())
.map(|msg| match msg {
VerticalMenuScreenMsg::Selected(i) => Some(FlowMsg::Choice(i)),
diff --git a/core/embed/rust/src/ui/layout_eckhart/flow/confirm_set_new_code.rs b/core/embed/rust/src/ui/layout_eckhart/flow/confirm_set_new_code.rs
index 8ba4ef47..3aacc19a 100644
--- a/core/embed/rust/src/ui/layout_eckhart/flow/confirm_set_new_code.rs
+++ b/core/embed/rust/src/ui/layout_eckhart/flow/confirm_set_new_code.rs
@@ -75,9 +75,10 @@ pub fn new_set_new_code(is_wipe_code: bool) -> Result<SwipeFlow, error::Error> {
_ => None,
});
- let content_menu = VerticalMenuScreen::new(VerticalMenu::<ShortMenuVec>::empty().with_item(
- Button::new_menu_item(TR::buttons__cancel.into(), theme::menu_item_title_orange()),
- ))
+ let content_menu = VerticalMenuScreen::new(
+ VerticalMenu::<ShortMenuVec>::empty()
+ .with_item(Button::new_cancel_menu_item(TR::buttons__cancel.into())),
+ )
.with_header(Header::new(title.into()).with_close_button())
.map(|msg| match msg {
VerticalMenuScreenMsg::Close => Some(FlowMsg::Cancelled),
diff --git a/core/embed/rust/src/ui/layout_eckhart/flow/confirm_summary.rs b/core/embed/rust/src/ui/layout_eckhart/flow/confirm_summary.rs
index 8a7bbb4e..0c1647e0 100644
--- a/core/embed/rust/src/ui/layout_eckhart/flow/confirm_summary.rs
+++ b/core/embed/rust/src/ui/layout_eckhart/flow/confirm_summary.rs
@@ -160,9 +160,8 @@ pub fn new_confirm_summary(
));
unwrap!(menu_items.push(MENU_ITEM_EXTRA_INFO));
}
- menu.item(Button::new_menu_item(
+ menu.item(Button::new_cancel_menu_item(
verb_cancel.unwrap_or(TR::buttons__cancel.into()),
- theme::menu_item_title_orange(),
));
unwrap!(menu_items.push(MENU_ITEM_CANCEL));
let content_menu = VerticalMenuScreen::new(menu)
diff --git a/core/embed/rust/src/ui/layout_eckhart/flow/confirm_value_intro.rs b/core/embed/rust/src/ui/layout_eckhart/flow/confirm_value_intro.rs
index 64205ad0..670a936d 100644
--- a/core/embed/rust/src/ui/layout_eckhart/flow/confirm_value_intro.rs
+++ b/core/embed/rust/src/ui/layout_eckhart/flow/confirm_value_intro.rs
@@ -117,10 +117,7 @@ pub fn new_confirm_value_intro(
value_menu_label,
theme::menu_item_title(),
))
- .with_item(Button::new_menu_item(
- cancel_menu_label,
- theme::menu_item_title_orange(),
- ));
+ .with_item(Button::new_cancel_menu_item(cancel_menu_label));
let content_menu = VerticalMenuScreen::new(menu_items)
.with_header(Header::new(TString::empty()).with_close_button())
diff --git a/core/embed/rust/src/ui/layout_eckhart/flow/confirm_with_menu.rs b/core/embed/rust/src/ui/layout_eckhart/flow/confirm_with_menu.rs
index c45b4f58..2157c012 100644
--- a/core/embed/rust/src/ui/layout_eckhart/flow/confirm_with_menu.rs
+++ b/core/embed/rust/src/ui/layout_eckhart/flow/confirm_with_menu.rs
@@ -67,8 +67,6 @@ pub fn new_confirm_with_menu<T: AllowedTextContent + MaybeTrace + 'static>(
extra_menu_label: Option<TString<'static>>,
cancel_menu_label: Option<TString<'static>>,
) -> Result<SwipeFlow, error::Error> {
- let cancel_menu_label = cancel_menu_label.unwrap_or(TR::buttons__cancel.into());
-
// Value
let confirm_button = if hold {
let verb = verb.unwrap_or(TR::buttons__hold_to_confirm.into());
@@ -109,9 +107,8 @@ pub fn new_confirm_with_menu<T: AllowedTextContent + MaybeTrace + 'static>(
unwrap!(menu_items.push(MENU_ITEM_INFO));
}
- menu.item(Button::new_menu_item(
- cancel_menu_label,
- theme::menu_item_title_orange(),
+ menu.item(Button::new_cancel_menu_item(
+ cancel_menu_label.unwrap_or(TR::buttons__cancel.into()),
));
unwrap!(menu_items.push(MENU_ITEM_CANCEL));
diff --git a/core/embed/rust/src/ui/layout_eckhart/flow/continue_recovery_homepage.rs b/core/embed/rust/src/ui/layout_eckhart/flow/continue_recovery_homepage.rs
index d882cab1..47fadfe6 100644
--- a/core/embed/rust/src/ui/layout_eckhart/flow/continue_recovery_homepage.rs
+++ b/core/embed/rust/src/ui/layout_eckhart/flow/continue_recovery_homepage.rs
@@ -246,9 +246,9 @@ fn flow_before_shares(
>,
cancel_btn: TString<'static>,
) -> Result<SwipeFlow, error::Error> {
- let content_menu = VerticalMenuScreen::new(VerticalMenu::<ShortMenuVec>::empty().with_item(
- Button::new_menu_item(cancel_btn, theme::menu_item_title_orange()),
- ))
+ let content_menu = VerticalMenuScreen::new(
+ VerticalMenu::<ShortMenuVec>::empty().with_item(Button::new_cancel_menu_item(cancel_btn)),
+ )
.with_header(Header::new(TString::empty()).with_close_button())
.map(|msg| match msg {
VerticalMenuScreenMsg::Selected(i) => Some(FlowMsg::Choice(i)),
@@ -282,10 +282,7 @@ fn flow_between_shares_simple(
TR::buttons__more_info.into(),
&theme::TEXT_MENU_ITEM_SUBTITLE,
))
- .with_item(Button::new_menu_item(
- TR::buttons__cancel.into(),
- theme::menu_item_title_orange(),
- )),
+ .with_item(Button::new_cancel_menu_item(TR::buttons__cancel.into())),
)
.with_header(Header::new(TR::recovery__title.into()).with_close_button())
.map(|msg| match msg {
@@ -342,10 +339,7 @@ fn flow_between_shares_advanced(
TR::recovery__title_remaining_shares.into(),
theme::menu_item_title(),
))
- .with_item(Button::new_menu_item(
- cancel_btn,
- theme::menu_item_title_orange(),
- )),
+ .with_item(Button::new_cancel_menu_item(cancel_btn)),
)
.with_header(Header::new(TString::empty()).with_close_button())
.map(|msg| match msg {
diff --git a/core/embed/rust/src/ui/layout_eckhart/flow/receive.rs b/core/embed/rust/src/ui/layout_eckhart/flow/receive.rs
index e557e640..8cf0817b 100644
--- a/core/embed/rust/src/ui/layout_eckhart/flow/receive.rs
+++ b/core/embed/rust/src/ui/layout_eckhart/flow/receive.rs
@@ -153,10 +153,7 @@ pub fn new_receive(
TR::address_details__account_info.into(),
theme::menu_item_title(),
))
- .with_item(Button::new_menu_item(
- TR::buttons__cancel.into(),
- theme::menu_item_title_orange(),
- )),
+ .with_item(Button::new_cancel_menu_item(TR::buttons__cancel.into())),
)
.with_header(Header::new(title).with_close_button())
.map(|msg| match msg {
diff --git a/core/embed/rust/src/ui/layout_eckhart/flow/request_number.rs b/core/embed/rust/src/ui/layout_eckhart/flow/request_number.rs
index edac5f8b..2d27df2e 100644
--- a/core/embed/rust/src/ui/layout_eckhart/flow/request_number.rs
+++ b/core/embed/rust/src/ui/layout_eckhart/flow/request_number.rs
@@ -97,10 +97,7 @@ pub fn new_request_number(
TR::buttons__more_info.into(),
theme::menu_item_title(),
))
- .with_item(Button::new_menu_item(
- TR::buttons__cancel.into(),
- theme::menu_item_title_orange(),
- ));
+ .with_item(Button::new_cancel_menu_item(TR::buttons__cancel.into()));
let content_menu = VerticalMenuScreen::new(menu_items)
.with_header(Header::new(title).with_close_button())
diff --git a/core/embed/rust/src/ui/layout_eckhart/flow/show_thp_pairing_code.rs b/core/embed/rust/src/ui/layout_eckhart/flow/show_thp_pairing_code.rs
index 7df84878..af42f53a 100644
--- a/core/embed/rust/src/ui/layout_eckhart/flow/show_thp_pairing_code.rs
+++ b/core/embed/rust/src/ui/layout_eckhart/flow/show_thp_pairing_code.rs
@@ -70,10 +70,7 @@ pub fn new_show_thp_pairing_code(
});
let mut menu = VerticalMenu::<ShortMenuVec>::empty();
- menu.item(Button::new_menu_item(
- TR::buttons__cancel.into(),
- theme::menu_item_title_orange(),
- ));
+ menu.item(Button::new_cancel_menu_item(TR::buttons__cancel.into()));
let menu_content = VerticalMenuScreen::new(menu)
.with_header(Header::new(TString::empty()).with_close_button())
Why this scored 15/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.