What changed, and why it matters
This commit adds a new tutorial screen to the Trezor hardware wallet's user interface that explains how to use the device's physical power button. It is a straightforward feature addition with no security relevance.
No security action required; this is a normal UI feature commit.
Security signals we found
No strong security signals were identified.
Evidence from the diff
The change introduces TutorialPowerScreen in the Eckhart layout, adds a new translated string tutorial__power, wires the screen into the ShowTutorial swipe flow between the navigation and menu steps, and updates UI tests to expect the new screen. It also updates translation metadata and signatures. No cryptographic, authentication, or sensitive logic is modified.
Changed components
core/embed/rust/src/ui/layout_eckhart/firmware/tutorial_screen.rscore/embed/rust/src/ui/layout_eckhart/flow/show_tutorial.rscore/embed/rust/src/translations/generated/translated_string.rscore/translations/en.jsontests/click_tests/test_tutorial_eckhart.pyInspect captured patch +133 / −10
diff --git a/core/embed/rust/librust_qstr.h b/core/embed/rust/librust_qstr.h
index 1853eb37..afb91860 100644
--- a/core/embed/rust/librust_qstr.h
+++ b/core/embed/rust/librust_qstr.h
@@ -880,6 +880,7 @@ static void _librust_qstrs(void) {
MP_QSTR_tutorial__middle_click;
MP_QSTR_tutorial__navigation_ts7;
MP_QSTR_tutorial__one_more_step;
+ MP_QSTR_tutorial__power;
MP_QSTR_tutorial__press_and_hold;
MP_QSTR_tutorial__ready_to_use;
MP_QSTR_tutorial__ready_to_use_safe5;
diff --git a/core/embed/rust/src/translations/generated/translated_string.rs b/core/embed/rust/src/translations/generated/translated_string.rs
index 6b88b433..28d97086 100644
--- a/core/embed/rust/src/translations/generated/translated_string.rs
+++ b/core/embed/rust/src/translations/generated/translated_string.rs
@@ -1542,6 +1542,7 @@ pub enum TranslatedString {
thp__continue_on_host = 1150, // {"Bolt": "", "Caesar": "", "Delizia": "", "Eckhart": "Continue on host"}
thp__pair_name = 1151, // {"Bolt": "", "Caesar": "", "Delizia": "", "Eckhart": "{0} is your Trezor's name."}
thp__pair_new_device = 1152, // {"Bolt": "", "Caesar": "", "Delizia": "", "Eckhart": "Pair with new device"}
+ tutorial__power = 1153, // "Use the power button on the side to turn your device on or off."
}
impl TranslatedString {
@@ -3521,6 +3522,7 @@ impl TranslatedString {
(Self::thp__pair_new_device, ""),
#[cfg(feature = "layout_eckhart")]
(Self::thp__pair_new_device, "Pair with new device"),
+ (Self::tutorial__power, "Use the power button on the side to turn your device on or off."),
];
#[cfg(feature = "micropython")]
@@ -4912,6 +4914,7 @@ impl TranslatedString {
(Qstr::MP_QSTR_tutorial__middle_click, Self::tutorial__middle_click),
(Qstr::MP_QSTR_tutorial__navigation_ts7, Self::tutorial__navigation_ts7),
(Qstr::MP_QSTR_tutorial__one_more_step, Self::tutorial__one_more_step),
+ (Qstr::MP_QSTR_tutorial__power, Self::tutorial__power),
(Qstr::MP_QSTR_tutorial__press_and_hold, Self::tutorial__press_and_hold),
(Qstr::MP_QSTR_tutorial__ready_to_use, Self::tutorial__ready_to_use),
(Qstr::MP_QSTR_tutorial__ready_to_use_safe5, Self::tutorial__ready_to_use_safe5),
diff --git a/core/embed/rust/src/ui/layout_eckhart/firmware/mod.rs b/core/embed/rust/src/ui/layout_eckhart/firmware/mod.rs
index 1e93c1fc..aa6d614b 100644
--- a/core/embed/rust/src/ui/layout_eckhart/firmware/mod.rs
+++ b/core/embed/rust/src/ui/layout_eckhart/firmware/mod.rs
@@ -46,7 +46,9 @@ pub use regulatory_screen::{RegulatoryMsg, RegulatoryScreen};
pub use select_word_screen::{SelectWordMsg, SelectWordScreen};
pub use share_words::{ShareWordsScreen, ShareWordsScreenMsg};
pub use text_screen::{AllowedTextContent, TextScreen, TextScreenMsg};
-pub use tutorial_screen::{TutorialWelcomeScreen, TutorialWelcomeScreenMsg};
+pub use tutorial_screen::{
+ TutorialPowerScreen, TutorialPowerScreenMsg, TutorialWelcomeScreen, TutorialWelcomeScreenMsg,
+};
pub use updatable_info_screen::{UpdatableInfoScreen, UpdatableInfoScreenMsg};
pub use value_input_screen::{
DurationInput, NumberInput, ValueInput, ValueInputScreen, ValueInputScreenMsg,
diff --git a/core/embed/rust/src/ui/layout_eckhart/firmware/tutorial_screen.rs b/core/embed/rust/src/ui/layout_eckhart/firmware/tutorial_screen.rs
index 9fb7215d..e7c01402 100644
--- a/core/embed/rust/src/ui/layout_eckhart/firmware/tutorial_screen.rs
+++ b/core/embed/rust/src/ui/layout_eckhart/firmware/tutorial_screen.rs
@@ -6,7 +6,7 @@ use crate::{
display::{toif::Toif, Color},
flow::Swipable,
geometry::{Alignment, Alignment2D, Insets, Offset, Point, Rect},
- shape::{Renderer, ToifImage},
+ shape::{Bar, Renderer, ToifImage},
util::{animation_disabled, Pager},
},
};
@@ -32,6 +32,8 @@ const LOADER_MAX_VAL: u16 = 1000;
const ICONS_PADDING: i16 = 15; // [px]
const LED_COLOR: Color = theme::LED_WHITE;
+const TOP_INSET: i16 = 38;
+
pub enum TutorialWelcomeScreenMsg {
Confirmed,
}
@@ -77,7 +79,7 @@ impl Component for TutorialWelcomeScreen {
debug_assert_eq!(bounds.width(), SCREEN.width());
let (rest, action_bar_area) = bounds.split_bottom(theme::ACTION_BAR_HEIGHT);
- let content_area = rest.inset(theme::SIDE_INSETS).inset(Insets::top(38));
+ let content_area = rest.inset(theme::SIDE_INSETS).inset(Insets::top(TOP_INSET));
self.text.place(content_area);
self.action_bar.place(action_bar_area);
@@ -170,3 +172,92 @@ impl crate::trace::Trace for TutorialWelcomeScreen {
t.component("TutorialWelcomeScreen");
}
}
+
+const BUTTON_WIDTH: i16 = 10;
+const BUTTON_HEIGHT: i16 = 96;
+const BUTTON_TOP_PADDING: i16 = 75;
+
+pub enum TutorialPowerScreenMsg {
+ Cancelled,
+ Confirmed,
+}
+
+pub struct TutorialPowerScreen {
+ text: Label<'static>,
+ action_bar: ActionBar,
+}
+
+impl TutorialPowerScreen {
+ pub fn new() -> Self {
+ Self {
+ text: Label::new(
+ TR::tutorial__power.into(),
+ Alignment::Start,
+ theme::firmware::TEXT_REGULAR,
+ )
+ .top_aligned(),
+ action_bar: ActionBar::new_double(
+ Button::with_icon(theme::ICON_CHEVRON_UP),
+ Button::with_text(TR::buttons__continue.into()),
+ ),
+ }
+ }
+}
+
+impl Component for TutorialPowerScreen {
+ type Msg = TutorialPowerScreenMsg;
+
+ fn place(&mut self, bounds: Rect) -> Rect {
+ debug_assert_eq!(bounds.height(), SCREEN.height());
+ debug_assert_eq!(bounds.width(), SCREEN.width());
+
+ let (rest, action_bar_area) = bounds.split_bottom(theme::ACTION_BAR_HEIGHT);
+ let content_area = rest.inset(theme::SIDE_INSETS).inset(Insets::top(TOP_INSET));
+
+ self.text.place(content_area);
+ self.action_bar.place(action_bar_area);
+
+ bounds
+ }
+
+ fn event(&mut self, ctx: &mut EventCtx, event: Event) -> Option<Self::Msg> {
+ match self.action_bar.event(ctx, event) {
+ Some(ActionBarMsg::Cancelled) => return Some(TutorialPowerScreenMsg::Cancelled),
+ Some(ActionBarMsg::Confirmed) => return Some(TutorialPowerScreenMsg::Confirmed),
+ _ => {}
+ };
+
+ None
+ }
+
+ fn render<'s>(&'s self, target: &mut impl Renderer<'s>) {
+ let rect = Rect::from_center_and_size(
+ Point::new(SCREEN.width(), BUTTON_TOP_PADDING + BUTTON_HEIGHT / 2),
+ Offset::new(2 * BUTTON_WIDTH, BUTTON_HEIGHT),
+ );
+ Bar::new(rect)
+ .with_radius(7)
+ .with_bg(theme::GREEN_LIME)
+ .render(target);
+ self.text.render(target);
+ self.action_bar.render(target);
+ }
+}
+
+#[cfg(feature = "micropython")]
+impl Swipable for TutorialPowerScreen {
+ fn get_swipe_config(&self) -> SwipeConfig {
+ SwipeConfig::new()
+ }
+
+ fn get_pager(&self) -> Pager {
+ Pager::single_page()
+ }
+}
+
+#[cfg(feature = "ui_debug")]
+impl crate::trace::Trace for TutorialPowerScreen {
+ fn trace(&self, t: &mut dyn crate::trace::Tracer) {
+ t.component("TutorialPowerScreen");
+ }
+}
diff --git a/core/embed/rust/src/ui/layout_eckhart/flow/show_tutorial.rs b/core/embed/rust/src/ui/layout_eckhart/flow/show_tutorial.rs
index 047aa457..9afccb68 100644
--- a/core/embed/rust/src/ui/layout_eckhart/flow/show_tutorial.rs
+++ b/core/embed/rust/src/ui/layout_eckhart/flow/show_tutorial.rs
@@ -19,8 +19,8 @@ use super::super::{
component::Button,
firmware::{
ActionBar, Header, HeaderMsg, Hint, ShortMenuVec, TextScreen, TextScreenMsg,
- TutorialWelcomeScreen, TutorialWelcomeScreenMsg, VerticalMenu, VerticalMenuScreen,
- VerticalMenuScreenMsg,
+ TutorialPowerScreen, TutorialPowerScreenMsg, TutorialWelcomeScreen,
+ TutorialWelcomeScreenMsg, VerticalMenu, VerticalMenuScreen, VerticalMenuScreenMsg,
},
theme::{self, Gradient, ScreenBackground},
};
@@ -34,6 +34,7 @@ pub enum ShowTutorial {
StepWelcome,
StepBegin,
StepNavigation,
+ StepPower,
StepMenu,
Menu,
MenuTropic,
@@ -60,8 +61,10 @@ impl FlowController for ShowTutorial {
match (self, msg) {
(Self::StepWelcome, FlowMsg::Confirmed) => Self::StepBegin.goto(),
(Self::StepBegin, FlowMsg::Confirmed) => Self::StepNavigation.goto(),
- (Self::StepNavigation, FlowMsg::Confirmed) => Self::StepMenu.goto(),
+ (Self::StepNavigation, FlowMsg::Confirmed) => Self::StepPower.goto(),
(Self::StepNavigation, FlowMsg::Cancelled) => Self::StepBegin.goto(),
+ (Self::StepPower, FlowMsg::Confirmed) => Self::StepMenu.goto(),
+ (Self::StepPower, FlowMsg::Cancelled) => Self::StepNavigation.goto(),
(Self::StepMenu, FlowMsg::Info) => Self::Menu.goto(),
(Self::Menu, FlowMsg::Choice(0)) => Self::StepHold.goto(),
(Self::Menu, FlowMsg::Choice(1)) => Self::MenuTropic.goto(),
@@ -135,6 +138,11 @@ pub fn new_show_tutorial() -> Result<SwipeFlow, error::Error> {
_ => None,
});
+ let content_step_power = TutorialPowerScreen::new().map(|msg| match msg {
+ TutorialPowerScreenMsg::Confirmed => Some(FlowMsg::Confirmed),
+ TutorialPowerScreenMsg::Cancelled => Some(FlowMsg::Cancelled),
+ });
+
let step_menu_paragraphs = Paragraphs::new([
Paragraph::new(&theme::TEXT_REGULAR, TR::tutorial__menu),
Paragraph::new(&theme::TEXT_REGULAR, TR::tutorial__menu_appendix),
@@ -288,6 +296,7 @@ pub fn new_show_tutorial() -> Result<SwipeFlow, error::Error> {
res.add_page(&ShowTutorial::StepWelcome, content_step_welcome)?
.add_page(&ShowTutorial::StepBegin, content_step_begin)?
.add_page(&ShowTutorial::StepNavigation, content_step_navigation)?
+ .add_page(&ShowTutorial::StepPower, content_step_power)?
.add_page(&ShowTutorial::StepMenu, content_step_menu)?
.add_page(&ShowTutorial::Menu, content_menu)?
.add_page(&ShowTutorial::MenuTropic, content_tropic())?
diff --git a/core/mocks/trezortranslate_keys.pyi b/core/mocks/trezortranslate_keys.pyi
index 57a88135..b44eef40 100644
--- a/core/mocks/trezortranslate_keys.pyi
+++ b/core/mocks/trezortranslate_keys.pyi
@@ -975,6 +975,7 @@ class TR:
tutorial__middle_click: str = "Press both left and right at the same\ntime to confirm."
tutorial__navigation_ts7: str = "Use buttons at the bottom to navigate and confirm your actions."
tutorial__one_more_step: str = "One more step"
+ tutorial__power: str = "Use the power button on the side to turn your device on or off."
tutorial__press_and_hold: str = "Press and hold the right button to\napprove important operations."
tutorial__ready_to_use: str = "You're ready to\nuse Trezor."
tutorial__ready_to_use_safe5: str = "You're all set to start using your device!"
diff --git a/core/translations/en.json b/core/translations/en.json
index f5bdb15e..95e29b57 100644
--- a/core/translations/en.json
+++ b/core/translations/en.json
@@ -1272,6 +1272,7 @@
"tutorial__middle_click": "Press both left and right at the same\ntime to confirm.",
"tutorial__navigation_ts7": "Use buttons at the bottom to navigate and confirm your actions.",
"tutorial__one_more_step": "One more step",
+ "tutorial__power": "Use the power button on the side to turn your device on or off.",
"tutorial__press_and_hold": "Press and hold the right button to\napprove important operations.",
"tutorial__ready_to_use": "You're ready to\nuse Trezor.",
"tutorial__ready_to_use_safe5": "You're all set to start using your device!",
diff --git a/core/translations/order.json b/core/translations/order.json
index a2034730..0ccbe9b6 100644
--- a/core/translations/order.json
+++ b/core/translations/order.json
@@ -1151,5 +1151,6 @@
"1149": "ble__pairing_title",
"1150": "thp__continue_on_host",
"1151": "thp__pair_name",
- "1152": "thp__pair_new_device"
+ "1152": "thp__pair_new_device",
+ "1153": "tutorial__power"
}
diff --git a/core/translations/signatures.json b/core/translations/signatures.json
index a74360bf..5d47c326 100644
--- a/core/translations/signatures.json
+++ b/core/translations/signatures.json
@@ -1,8 +1,8 @@
{
"current": {
- "merkle_root": "093587dc05beb27c037b3f17792d009e9ec39d67d3f9d8f7af1fa6226978c661",
- "datetime": "2025-09-11T15:57:43.128589+00:00",
- "commit": "6baa4f3e77ba97dbca269a1994c4cb5f0eb748d3"
+ "merkle_root": "340f1b8642e284a1c31c3df08e79fbf3be01be0c491718c164bd425b0c23e16c",
+ "datetime": "2025-09-12T06:33:58.713863+00:00",
+ "commit": "5cef52bec8690b861c0381899df32803a3809e5e"
},
"history": [
{
diff --git a/tests/click_tests/test_tutorial_eckhart.py b/tests/click_tests/test_tutorial_eckhart.py
index f2710ddb..37f61c23 100644
--- a/tests/click_tests/test_tutorial_eckhart.py
+++ b/tests/click_tests/test_tutorial_eckhart.py
@@ -73,6 +73,11 @@ def _assert_navigation_screen_and_proceed(debug: "DebugLink"):
debug.click(debug.screen_buttons.ok())
+def _assert_power_screen_and_proceed(debug: "DebugLink"):
+ assert "TutorialPowerScreen" in debug.read_layout().all_components()
+ debug.click(debug.screen_buttons.ok())
+
+
def _assert_instructions_and_proceed_to_menu(debug: "DebugLink"):
layout = debug.read_layout()
assert layout.title() == TR.tutorial__title_handy_menu
@@ -104,6 +109,7 @@ def test_tutorial_full_completion(device_handler: "BackgroundDeviceHandler"):
_wait_for_welcome_animation(debug)
_assert_begin_screen_and_proceed(debug)
_assert_navigation_screen_and_proceed(debug)
+ _assert_power_screen_and_proceed(debug)
_assert_instructions_and_proceed_to_menu(debug)
# menu screen - continue
@@ -129,6 +135,7 @@ def test_tutorial_cancel_from_main_menu(device_handler: "BackgroundDeviceHandler
_wait_for_welcome_animation(debug)
_assert_begin_screen_and_proceed(debug)
_assert_navigation_screen_and_proceed(debug)
+ _assert_power_screen_and_proceed(debug)
_assert_instructions_and_proceed_to_menu(debug)
# menu screen - exit
@@ -154,6 +161,7 @@ def test_tutorial_cancel_from_confirm_menu(device_handler: "BackgroundDeviceHand
_wait_for_welcome_animation(debug)
_assert_begin_screen_and_proceed(debug)
_assert_navigation_screen_and_proceed(debug)
+ _assert_power_screen_and_proceed(debug)
_assert_instructions_and_proceed_to_menu(debug)
# menu screen - continue
@@ -182,6 +190,7 @@ def test_tutorial_menu_close(device_handler: "BackgroundDeviceHandler"):
_wait_for_welcome_animation(debug)
_assert_begin_screen_and_proceed(debug)
_assert_navigation_screen_and_proceed(debug)
+ _assert_power_screen_and_proceed(debug)
_assert_instructions_and_proceed_to_menu(debug)
# menu screen
@@ -231,6 +240,7 @@ def test_tutorial_menu_tropic(device_handler: "BackgroundDeviceHandler"):
_wait_for_welcome_animation(debug)
_assert_begin_screen_and_proceed(debug)
_assert_navigation_screen_and_proceed(debug)
+ _assert_power_screen_and_proceed(debug)
_assert_instructions_and_proceed_to_menu(debug)
# menu screen
@@ -287,6 +297,7 @@ def test_tutorial_restart(device_handler: "BackgroundDeviceHandler"):
_wait_for_welcome_animation(debug)
_assert_begin_screen_and_proceed(debug)
_assert_navigation_screen_and_proceed(debug)
+ _assert_power_screen_and_proceed(debug)
_assert_instructions_and_proceed_to_menu(debug)
# menu screen - restart
@@ -295,6 +306,7 @@ def test_tutorial_restart(device_handler: "BackgroundDeviceHandler"):
_assert_begin_screen_and_proceed(debug)
_assert_navigation_screen_and_proceed(debug)
+ _assert_power_screen_and_proceed(debug)
_assert_instructions_and_proceed_to_menu(debug)
# menu screen - continue
@@ -311,6 +323,7 @@ def test_tutorial_restart(device_handler: "BackgroundDeviceHandler"):
_assert_begin_screen_and_proceed(debug)
_assert_navigation_screen_and_proceed(debug)
+ _assert_power_screen_and_proceed(debug)
_assert_instructions_and_proceed_to_menu(debug)
# menu screen - exit
@@ -330,6 +343,7 @@ def test_tutorial_restart(device_handler: "BackgroundDeviceHandler"):
_assert_begin_screen_and_proceed(debug)
_assert_navigation_screen_and_proceed(debug)
+ _assert_power_screen_and_proceed(debug)
_assert_instructions_and_proceed_to_menu(debug)
# menu screen - continue
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.