Repository: trezor/trezor-firmware
Pull request #5065: refactor(core): return uPy Obj from DeviceMenu
Author: login: obrusvit
Labels: name: core
description: Trezor Core firmware. Runs on Trezor Model T and Safe models.
Created: 2025-05-19T22:32:27Z | Merged: 2025-05-20T21:56:56Z

[no changelog]

<!--
For core developers:
- Assign yourself to the PR.
- Set the priority to match the original issue.
- Add the PR to the current sprint.
- If it's a draft PR, mark it as "In Progress."
- If it's a final PR, mark it as "Needs Review."

For external contributors:
- Please open an issue before submitting a PR so we can discuss whether we want to proceed with it.
-->



Diff:
diff --git a/core/embed/rust/librust_qstr.h b/core/embed/rust/librust_qstr.h
index 2be723d6fe2..eb84b0fb35a 100644
--- a/core/embed/rust/librust_qstr.h
+++ b/core/embed/rust/librust_qstr.h
@@ -18,12 +18,18 @@ static void _librust_qstrs(void) {
   MP_QSTR_;
   MP_QSTR_ATTACHED;
   MP_QSTR_AttachType;
+  MP_QSTR_AutoLockDelay;
   MP_QSTR_BacklightLevels;
+  MP_QSTR_BackupFailed;
   MP_QSTR_BleInterface;
   MP_QSTR_CANCELLED;
   MP_QSTR_CONFIRMED;
+  MP_QSTR_CheckBackup;
   MP_QSTR_DIM;
   MP_QSTR_DONE;
+  MP_QSTR_DeviceDisconnect;
+  MP_QSTR_DeviceMenuResult;
+  MP_QSTR_DevicePair;
   MP_QSTR_INFO;
   MP_QSTR_INITIAL;
   MP_QSTR_LOW;
@@ -42,10 +48,12 @@ static void _librust_qstrs(void) {
   MP_QSTR_SWIPE_LEFT;
   MP_QSTR_SWIPE_RIGHT;
   MP_QSTR_SWIPE_UP;
+  MP_QSTR_ScreenBrightness;
   MP_QSTR_TR;
   MP_QSTR_TRANSITIONING;
   MP_QSTR_TX_PACKET_LEN;
   MP_QSTR_TranslationsHeader;
+  MP_QSTR_WipeDevice;
   MP_QSTR___del__;
   MP_QSTR___dict__;
   MP_QSTR___name__;
diff --git a/core/embed/rust/src/ui/api/firmware_micropython.rs b/core/embed/rust/src/ui/api/firmware_micropython.rs
index b10280d89e4..173f599cf07 100644
--- a/core/embed/rust/src/ui/api/firmware_micropython.rs
+++ b/core/embed/rust/src/ui/api/firmware_micropython.rs
@@ -17,6 +17,7 @@ use crate::{
         component::Empty,
         layout::{
             base::LAYOUT_STATE,
+            device_menu_result::DEVICE_MENU_RESULT,
             obj::{ComponentMsgObj, LayoutObj, ATTACH_TYPE_OBJ},
             result::{CANCELLED, CONFIRMED, INFO},
             util::{upy_disable_animation, RecoveryType},
@@ -1651,7 +1652,7 @@ pub static mp_module_trezorui_api: Module = obj_module! {
     ///     device_name: str,
     ///     paired_devices: Iterable[str],
     ///     auto_lock_delay: str,
-    /// ) -> LayoutObj[UiResult]:
+    /// ) -> LayoutObj[UiResult | DeviceMenuResult | tuple[DeviceMenuResult, int]]:
     ///     """Show the device menu."""
     Qstr::MP_QSTR_show_device_menu => obj_fn_kw!(0, new_show_device_menu).as_obj(),
 
@@ -1820,4 +1821,14 @@ pub static mp_module_trezorui_api: Module = obj_module! {
     ///     DONE: "ClassVar[LayoutState]"
     Qstr::MP_QSTR_LayoutState => LAYOUT_STATE.as_obj(),
 
+    /// class DeviceMenuResult:
+    ///     """Result of a device menu operation."""
+    ///     BackupFailed: ClassVar[DeviceMenuResult]
+    ///     DevicePair: ClassVar[DeviceMenuResult]
+    ///     DeviceDisconnect: ClassVar[DeviceMenuResult]
+    ///     CheckBackup: ClassVar[DeviceMenuResult]
+    ///     WipeDevice: ClassVar[DeviceMenuResult]
+    ///     ScreenBrightness: ClassVar[DeviceMenuResult]
+    ///     AutoLockDelay: ClassVar[DeviceMenuResult]
+    Qstr::MP_QSTR_DeviceMenuResult => DEVICE_MENU_RESULT.as_obj(),
 };
diff --git a/core/embed/rust/src/ui/layout/device_menu_result.rs b/core/embed/rust/src/ui/layout/device_menu_result.rs
new file mode 100644
index 00000000000..e1e071272e3
--- /dev/null
+++ b/core/embed/rust/src/ui/layout/device_menu_result.rs
@@ -0,0 +1,32 @@
+use crate::micropython::{
+    macros::{obj_dict, obj_map, obj_type},
+    qstr::Qstr,
+    simple_type::SimpleTypeObj,
+    typ::Type,
+};
+
+static DEVICE_MENU_RESULT_BASE_TYPE: Type = obj_type! { name: Qstr::MP_QSTR_DeviceMenuResult, };
+
+pub static BACKUP_FAILED: SimpleTypeObj = SimpleTypeObj::new(&DEVICE_MENU_RESULT_BASE_TYPE);
+pub static DEVICE_PAIR: SimpleTypeObj = SimpleTypeObj::new(&DEVICE_MENU_RESULT_BASE_TYPE);
+pub static DEVICE_DISCONNECT: SimpleTypeObj = SimpleTypeObj::new(&DEVICE_MENU_RESULT_BASE_TYPE);
+pub static CHECK_BACKUP: SimpleTypeObj = SimpleTypeObj::new(&DEVICE_MENU_RESULT_BASE_TYPE);
+pub static WIPE_DEVICE: SimpleTypeObj = SimpleTypeObj::new(&DEVICE_MENU_RESULT_BASE_TYPE);
+pub static SCREEN_BRIGHTNESS: SimpleTypeObj = SimpleTypeObj::new(&DEVICE_MENU_RESULT_BASE_TYPE);
+pub static AUTO_LOCK_DELAY: SimpleTypeObj = SimpleTypeObj::new(&DEVICE_MENU_RESULT_BASE_TYPE);
+
+// Create a DeviceMenuResult class that contains all result types
+static DEVICE_MENU_RESULT_TYPE: Type = obj_type! {
+    name: Qstr::MP_QSTR_DeviceMenuResult,
+    locals: &obj_dict! { obj_map! {
+        Qstr::MP_QSTR_BackupFailed => BACKUP_FAILED.as_obj(),
+        Qstr::MP_QSTR_DevicePair => DEVICE_PAIR.as_obj(),
+        Qstr::MP_QSTR_DeviceDisconnect => DEVICE_DISCONNECT.as_obj(),
+        Qstr::MP_QSTR_CheckBackup => CHECK_BACKUP.as_obj(),
+        Qstr::MP_QSTR_WipeDevice => WIPE_DEVICE.as_obj(),
+        Qstr::MP_QSTR_ScreenBrightness => SCREEN_BRIGHTNESS.as_obj(),
+        Qstr::MP_QSTR_AutoLockDelay => AUTO_LOCK_DELAY.as_obj(),
+    } },
+};
+
+pub static DEVICE_MENU_RESULT: SimpleTypeObj = SimpleTypeObj::new(&DEVICE_MENU_RESULT_TYPE);
diff --git a/core/embed/rust/src/ui/layout/mod.rs b/core/embed/rust/src/ui/layout/mod.rs
index 42c3f6bd3ea..c5fbdb44fd5 100644
--- a/core/embed/rust/src/ui/layout/mod.rs
+++ b/core/embed/rust/src/ui/layout/mod.rs
@@ -3,6 +3,8 @@ pub mod base;
 #[cfg(feature = "micropython")]
 pub mod obj;
 
+#[cfg(feature = "micropython")]
+pub mod device_menu_result;
 #[cfg(feature = "micropython")]
 pub mod result;
 
diff --git a/core/embed/rust/src/ui/layout_eckhart/component_msg_obj.rs b/core/embed/rust/src/ui/layout_eckhart/component_msg_obj.rs
index 6d03c043c2c..ffbaa304f64 100644
--- a/core/embed/rust/src/ui/layout_eckhart/component_msg_obj.rs
+++ b/core/embed/rust/src/ui/layout_eckhart/component_msg_obj.rs
@@ -1,12 +1,13 @@
 use crate::{
     error::Error,
-    micropython::obj::Obj,
+    micropython::{obj::Obj, util::new_tuple},
     ui::{
         component::{
             text::paragraphs::{ParagraphSource, Paragraphs},
             Component, Timeout,
         },
         layout::{
+            device_menu_result::*,
             obj::ComponentMsgObj,
             result::{CANCELLED, CONFIRMED, INFO},
         },
@@ -148,13 +149,15 @@ impl ComponentMsgObj for SetBrightnessScreen {
 impl<'a> ComponentMsgObj for DeviceMenuScreen<'a> {
     fn msg_try_into_obj(&self, msg: Self::Msg) -> Result<Obj, Error> {
         match msg {
-            DeviceMenuMsg::BackupFailed => "BackupFailed".try_into(),
-            DeviceMenuMsg::DevicePair => "DevicePair".try_into(),
-            DeviceMenuMsg::DeviceDisconnect(_) => "DeviceDisconnect".try_into(),
-            DeviceMenuMsg::CheckBackup => "CheckBackup".try_into(),
-            DeviceMenuMsg::WipeDevice => "WipeDevice".try_into(),
-            DeviceMenuMsg::ScreenBrightness => "ScreenBrightness".try_into(),
-            DeviceMenuMsg::AutoLockDelay => "AutoLockDelay".try_into(),
+            DeviceMenuMsg::BackupFailed => Ok(BACKUP_FAILED.as_obj()),
+            DeviceMenuMsg::DevicePair => Ok(DEVICE_PAIR.as_obj()),
+            DeviceMenuMsg::DeviceDisconnect(index) => {
+                Ok(new_tuple(&[DEVICE_DISCONNECT.as_obj(), index.try_into()?])?)
+            }
+            DeviceMenuMsg::CheckBackup => Ok(CHECK_BACKUP.as_obj()),
+            DeviceMenuMsg::WipeDevice => Ok(WIPE_DEVICE.as_obj()),
+            DeviceMenuMsg::ScreenBrightness => Ok(SCREEN_BRIGHTNESS.as_obj()),
+            DeviceMenuMsg::AutoLockDelay => Ok(AUTO_LOCK_DELAY.as_obj()),
             DeviceMenuMsg::Close => Ok(CANCELLED.as_obj()),
         }
     }
diff --git a/core/mocks/generated/trezorui_api.pyi b/core/mocks/generated/trezorui_api.pyi
index 3047249c3e8..5f0c6862c86 100644
--- a/core/mocks/generated/trezorui_api.pyi
+++ b/core/mocks/generated/trezorui_api.pyi
@@ -554,7 +554,7 @@ def show_device_menu(
     device_name: str,
     paired_devices: Iterable[str],
     auto_lock_delay: str,
-) -> LayoutObj[UiResult]:
+) -> LayoutObj[UiResult | DeviceMenuResult | tuple[DeviceMenuResult, int]]:
     """Show the device menu."""
 
 
@@ -738,3 +738,15 @@ class LayoutState:
     ATTACHED: "ClassVar[LayoutState]"
     TRANSITIONING: "ClassVar[LayoutState]"
     DONE: "ClassVar[LayoutState]"
+
+
+# rust/src/ui/api/firmware_micropython.rs
+class DeviceMenuResult:
+    """Result of a device menu operation."""
+    BackupFailed: ClassVar[DeviceMenuResult]
+    DevicePair: ClassVar[DeviceMenuResult]
+    DeviceDisconnect: ClassVar[DeviceMenuResult]
+    CheckBackup: ClassVar[DeviceMenuResult]
+    WipeDevice: ClassVar[DeviceMenuResult]
+    ScreenBrightness: ClassVar[DeviceMenuResult]
+    AutoLockDelay: ClassVar[DeviceMenuResult]
diff --git a/core/src/apps/homescreen/device_menu.py b/core/src/apps/homescreen/device_menu.py
index dfdac3193fa..400b4416d3d 100644
--- a/core/src/apps/homescreen/device_menu.py
+++ b/core/src/apps/homescreen/device_menu.py
@@ -1,9 +1,9 @@
 import storage.device
 import trezorui_api
 from trezor import TR, config, utils
-from trezor.ui.layouts import raise_if_not_confirmed
-from trezor.ui.layouts.common import interact
+from trezor.ui.layouts import interact
 from trezor.wire import ActionCancelled
+from trezorui_api import DeviceMenuResult
 
 
 async def _prompt_auto_lock_delay() -> int:
@@ -61,7 +61,7 @@ async def handle_device_menu() -> None:
         "{count} {plural}", auto_lock_num, auto_lock_label
     )
 
-    menu_result = await raise_if_not_confirmed(
+    menu_result = await interact(
         trezorui_api.show_device_menu(
             failed_backup=failed_backup,
             battery_percentage=battery_percentage,
@@ -70,27 +70,37 @@ async def handle_device_menu() -> None:
             device_name=device_name,
             auto_lock_delay=auto_lock_str,
         ),
-        None,
+        "device_menu",
     )
-    if menu_result == "DevicePair":
+
+    if menu_result is DeviceMenuResult.DevicePair:
         from apps.management.ble.pair_new_device import pair_new_device
 
         await pair_new_device()
-    elif menu_result == "ScreenBrightness":
+    elif menu_result is DeviceMenuResult.ScreenBrightness:
         from trezor.ui.layouts import set_brightness
 
         await set_brightness()
-    elif menu_result == "WipeDevice":
+    elif menu_result is DeviceMenuResult.WipeDevice:
         from trezor.messages import WipeDevice
 
         from apps.management.wipe_device import wipe_device
 
         await wipe_device(WipeDevice())
-    elif menu_result == "AutoLockDelay":
+    elif menu_result is DeviceMenuResult.AutoLockDelay:
 
         if config.has_pin():
 
             auto_lock_delay_ms = await _prompt_auto_lock_delay()
             storage.device.set_autolock_delay_ms(auto_lock_delay_ms)
+    elif isinstance(menu_result, tuple):
+        # It's a tuple with (result_type, index)
+        result_type, index = menu_result
+        if result_type is DeviceMenuResult.DeviceDisconnect:
+            raise RuntimeError(
+                f"Device disconnect not implemented, device index: {index}"
+            )
+        else:
+            raise RuntimeError(f"Unknown menu {result_type}, {index}")
     else:
         raise RuntimeError(f"Unknown menu {menu_result}")


Reviews:
user: login: romanz
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-20T03:05:26Z
user: login: romanz
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-20T03:07:36Z
user: login: obrusvit
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-20T13:31:18Z
user: login: obrusvit
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-20T13:31:37Z
user: login: romanz
body: LGTM, thanks!
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-05-20T15:15:13Z

Review comments:
diff_hunk: @@ -72,21 +73,21 @@ async def handle_device_menu() -> None:
         ),
         None,
     )
-    if menu_result == "DevicePair":
+    if menu_result == DeviceMenuResult.DevicePair:
path: core/src/apps/homescreen/device_menu.py
user: login: romanz
body: I think it's OK to use `is` here instead of `==` (since we are working with singletons, right?)
created_at: 2025-05-20T03:05:25Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,38 @@
+use crate::micropython::{
+    macros::{obj_dict, obj_map, obj_type},
+    qstr::Qstr,
+    simple_type::SimpleTypeObj,
+    typ::Type,
+};
+
+static BACKUP_FAILED_TYPE: Type = obj_type! { name: Qstr::MP_QSTR_BackupFailed, };
path: core/embed/rust/src/ui/layout/device_menu_result.rs
user: login: romanz
body: Not sure if we need the result objects to have distinct types... can we use a single type for all of them?
created_at: 2025-05-20T03:07:36Z
author_association: CONTRIBUTOR
diff_hunk: @@ -72,21 +73,21 @@ async def handle_device_menu() -> None:
         ),
         None,
     )
-    if menu_result == "DevicePair":
+    if menu_result == DeviceMenuResult.DevicePair:
path: core/src/apps/homescreen/device_menu.py
user: login: obrusvit
body: https://github.com/trezor/trezor-firmware/pull/5065/commits/385b4556fc8da04cc2ecda3ec20248f0b30c3ac4
created_at: 2025-05-20T13:31:17Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,38 @@
+use crate::micropython::{
+    macros::{obj_dict, obj_map, obj_type},
+    qstr::Qstr,
+    simple_type::SimpleTypeObj,
+    typ::Type,
+};
+
+static BACKUP_FAILED_TYPE: Type = obj_type! { name: Qstr::MP_QSTR_BackupFailed, };
path: core/embed/rust/src/ui/layout/device_menu_result.rs
user: login: obrusvit
body: Indeed, we can have just one base type:
https://github.com/trezor/trezor-firmware/pull/5065/commits/385b4556fc8da04cc2ecda3ec20248f0b30c3ac4
created_at: 2025-05-20T13:31:37Z
author_association: CONTRIBUTOR

PR comments:
user: login: github-actions[bot]
created_at: 2025-05-19T22:33:45Z
body: <!-- ui-comment-Core -->
|core UI changes|device test|click test|persistence test|
|---------------|-----------|----------|----------------|
|T2T1 Model T   |<img src="https://data.trezor.io/dev/firmware/ui_report/15147601424/T2T1-en-core_device_test/status.png" width="24" height="20px" /> [test](https://data.trezor.io/dev/firmware/ui_report/15147601424/T2T1-en-core_device_test/index.html)([screens](https://data.trezor.io/dev/firmware/ui_report/15147601424/T2T1-en-core_device_test/differing_screens.html)) [main](https://data.trezor.io/dev/firmware/ui_report/15147601424/T2T1-en-core_device_test/master_index.html)([screens](https://data.trezor.io/dev/firmware/ui_report/15147601424/T2T1-en-core_device_test/master_diff.html)) |<img src="https://data.trezor.io/dev/firmware/ui_report/15147601424/T2T1-en-core_click_test/status.png" width="20px" height="20px" /> [test](https://data.trezor.io/dev/firmware/ui_report/15147601424/T2T1-en-core_click_test/index.html)([screens](https://data.trezor.io/dev/firmware/ui_report/15147601424/T2T1-en-core_click_test/differing_screens.html)) [main](https://data.trezor.io/dev/firmware/ui_report/15147601424/T2T1-en-core_click_test/master_index.html)([screens](https://data.trezor.io/dev/firmware/ui_report/15147601424/T2T1-en-core_click_test/master_diff.html)) |<img src="https://data.trezor.io/dev/firmware/ui_report/15147601424/T2T1-en-core_persistence_test/status.png" width="20px" height="20px" /> [test](https://data.trezor.io/dev/firmware/ui_report/15147601424/T2T1-en-core_persistence_test/index.html)([screens](https://data.trezor.io/dev/firmware/ui_report/15147601424/T2T1-en-core_persistence_test/differing_screens.html)) [main](https://data.trezor.io/dev/firmware/ui_report/15147601424/T2T1-en-core_persistence_test/master_index.html)([screens](https://data.trezor.io/dev/firmware/ui_report/15147601424/T2T1-en-core_persistence_test/master_diff.html))||
|T3B1 Safe 3    |<img src="https://data.trezor.io/dev/firmware/ui_report/15147601424/T3B1-en-core_device_test/status.png" width="20px" height="20px" /> [test](https://data.trezor.io/dev/firmware/ui_report/15147601424/T3B1-en-core_device_test/index.html)([screens](https://data.trezor.io/dev/firmware/ui_report/15147601424/T3B1-en-core_device_test/differing_screens.html)) [main](https://data.trezor.io/dev/firmware/ui_report/15147601424/T3B1-en-core_device_test/master_index.html)([screens](https://data.trezor.io/dev/firmware/ui_report/15147601424/T3B1-en-core_device_test/master_diff.html)) |<img src="https://data.trezor.io/dev/firmware/ui_report/15147601424/T3B1-en-core_click_test/status.png" width="20px" height="20px" /> [test](https://data.trezor.io/dev/firmware/ui_report/15147601424/T3B1-en-core_click_test/index.html)([screens](https://data.trezor.io/dev/firmware/ui_report/15147601424/T3B1-en-core_click_test/differing_screens.html)) [main](https://data.trezor.io/dev/firmware/ui_report/15147601424/T3B1-en-core_click_test/master_index.html)([screens](https://data.trezor.io/dev/firmware/ui_report/15147601424/T3B1-en-core_click_test/master_diff.html)) |<img src="https://data.trezor.io/dev/firmware/ui_report/15147601424/T3B1-en-core_persistence_test/status.png" width="20px" height="20px" /> [test](https://data.trezor.io/dev/firmware/ui_report/15147601424/T3B1-en-core_persistence_test/index.html)([screens](https://data.trezor.io/dev/firmware/ui_report/15147601424/T3B1-en-core_persistence_test/differing_screens.html)) [main](https://data.trezor.io/dev/firmware/ui_report/15147601424/T3B1-en-core_persistence_test/master_index.html)([screens](https://data.trezor.io/dev/firmware/ui_report/15147601424/T3B1-en-core_persistence_test/master_diff.html))||
|T3T1 Safe 5    |<img src="https://data.trezor.io/dev/firmware/ui_report/15147601424/T3T1-en-core_device_test/status.png" width="20px" height="20px" /> [test](https://data.trezor.io/dev/firmware/ui_report/15147601424/T3T1-en-core_device_test/index.html)([screens](https://data.trezor.io/dev/firmware/ui_report/15147601424/T3T1-en-core_device_test/differing_screens.html)) [main](https://data.trezor.io/dev/firmware/ui_report/15147601424/T3T1-en-core_device_test/master_index.html)([screens](https://data.trezor.io/dev/firmware/ui_report/15147601424/T3T1-en-core_device_test/master_diff.html)) |<img src="https://data.trezor.io/dev/firmware/ui_report/15147601424/T3T1-en-core_click_test/status.png" width="20px" height="20px" /> [test](https://data.trezor.io/dev/firmware/ui_report/15147601424/T3T1-en-core_click_test/index.html)([screens](https://data.trezor.io/dev/firmware/ui_report/15147601424/T3T1-en-core_click_test/differing_screens.html)) [main](https://data.trezor.io/dev/firmware/ui_report/15147601424/T3T1-en-core_click_test/master_index.html)([screens](https://data.trezor.io/dev/firmware/ui_report/15147601424/T3T1-en-core_click_test/master_diff.html)) |<img src="https://data.trezor.io/dev/firmware/ui_report/15147601424/T3T1-en-core_persistence_test/status.png" width="20px" height="20px" /> [test](https://data.trezor.io/dev/firmware/ui_report/15147601424/T3T1-en-core_persistence_test/index.html)([screens](https://data.trezor.io/dev/firmware/ui_report/15147601424/T3T1-en-core_persistence_test/differing_screens.html)) [main](https://data.trezor.io/dev/firmware/ui_report/15147601424/T3T1-en-core_persistence_test/master_index.html)([screens](https://data.trezor.io/dev/firmware/ui_report/15147601424/T3T1-en-core_persistence_test/master_diff.html))||
|T3W1           |<img src="https://data.trezor.io/dev/firmware/ui_report/15147601424/T3W1-en-core_device_test/status.png" width="20px" height="20px" /> [test](https://data.trezor.io/dev/firmware/ui_report/15147601424/T3W1-en-core_device_test/index.html)([screens](https://data.trezor.io/dev/firmware/ui_report/15147601424/T3W1-en-core_device_test/differing_screens.html)) [main](https://data.trezor.io/dev/firmware/ui_report/15147601424/T3W1-en-core_device_test/master_index.html)([screens](https://data.trezor.io/dev/firmware/ui_report/15147601424/T3W1-en-core_device_test/master_diff.html)) |<img src="https://data.trezor.io/dev/firmware/ui_report/15147601424/T3W1-en-core_click_test/status.png" width="20px" height="20px" /> [test](https://data.trezor.io/dev/firmware/ui_report/15147601424/T3W1-en-core_click_test/index.html)([screens](https://data.trezor.io/dev/firmware/ui_report/15147601424/T3W1-en-core_click_test/differing_screens.html)) [main](https://data.trezor.io/dev/firmware/ui_report/15147601424/T3W1-en-core_click_test/master_index.html)([screens](https://data.trezor.io/dev/firmware/ui_report/15147601424/T3W1-en-core_click_test/master_diff.html)) |<img src="https://data.trezor.io/dev/firmware/ui_report/15147601424/T3W1-en-core_persistence_test/status.png" width="20px" height="20px" /> [test](https://data.trezor.io/dev/firmware/ui_report/15147601424/T3W1-en-core_persistence_test/index.html)([screens](https://data.trezor.io/dev/firmware/ui_report/15147601424/T3W1-en-core_persistence_test/differing_screens.html)) [main](https://data.trezor.io/dev/firmware/ui_report/15147601424/T3W1-en-core_persistence_test/master_index.html)([screens](https://data.trezor.io/dev/firmware/ui_report/15147601424/T3W1-en-core_persistence_test/master_diff.html))||
|All            |<img src="https://data.trezor.io/dev/firmware/master_diff/15147601424/status.png" width="20px" height="20px" /> [main](https://data.trezor.io/dev/firmware/master_diff/15147601424/index.html)([screens](https://data.trezor.io/dev/firmware/master_diff/15147601424/master_diff.html)) ||
author_association: NONE
user: login: obrusvit
created_at: 2025-05-20T13:32:48Z
body: @romanz I also created a preparation for returning index to disconnect device
https://github.com/trezor/trezor-firmware/pull/5065/commits/0a2e21076a7eac488a79de091bcbd0ed7c7a95ef
author_association: CONTRIBUTOR
user: login: romanz
created_at: 2025-05-20T15:16:23Z
body: Rust unittests have failed, but I am not sure it's related to this PR:
```
thread 'ui::layout_bolt::component::page::tests::paragraphs_three_long' panicked at src/ui/layout_bolt/component/page.rs:728:9:
assertion `left == right` failed
  left: Object {"active_page": Number(0), "component": String("ButtonPage"), "content": Object {"component": String("Paragraphs"), "paragraphs": Array [Array [String("This paragraph is"), String("\n"), String("using a bold font. It"), String("\n"), String("doesn't need to be all"), String("\n"), String("that long.")], Array [String("And this one is using"), String("\n"), String("MONO. Monospace is"), String("\n"), String("nice for numbers"), String("...")]]}, "hold": Bool(false), "page_count": Number(3)}
 right: Object {"active_page": Number(0), "component": String("ButtonPage"), "content": Object {"component": String("Paragraphs"), "paragraphs": Array [Array [String("This paragraph is"), String("\n"), String("using a bold font. It"), String("\n"), String("doesn't need to be all"), String("\n"), String("that long.")], Array [String("And this one is u"), String("\n"), String("sing MONO. Monosp"), String("\n"), String("ace is nice f"), String("...")]]}, "hold": Bool(false), "page_count": Number(3)}
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
test ui::layout_bolt::component::page::tests::paragraphs_three_long ... FAILED
```
author_association: CONTRIBUTOR
user: login: obrusvit
created_at: 2025-05-20T20:48:50Z
body: > Rust unittests have failed, but I am not sure it's related to this PR:

That's a problem related to `format_pairing_code` test in `ui-eckhart`. I will fix it there.
author_association: CONTRIBUTOR