What changed, and why it matters
This commit refactors the user interface for entering numbers on the Trezor Safe 5 (Delizia layout). It replaces a Rust-based multi-page 'flow' with a simpler screen that hands the 'more info' menu back to Python code. It also adds a new full-screen text component that can paginate long information. There is no obvious security bug in the diff, but the change touches code that handles user confirmation during device setup and backup, so any mistake could affect what the user sees or approves.
No immediate security action required. Treat as a normal UI refactor; verify with existing device tests that number-input and info-screen flows still behave correctly, especially pagination, cancellation, and the return of the selected value to Python.
Security signals we found
Refactor of user confirmation/number-input UI path
Removal of Rust-side state (AtomicU16 NUM_DISPLAYED) in favor of returning value to Python
New pagination logic in MoreInfoScreen with first-pass layout measurement
Styling decision now depends on a new `is_data` boolean from Python for info screens
Evidence from the diff
The patch removes the Rust SwipeFlow request_number implementation and the UpdatableMoreInfo component, and introduces MoreInfoScreen, a self-paginating full-screen text component. request_number is now implemented as a single Frame<SwipeContent<NumberInputDialog>> with an external menu button; the current value is returned to Python alongside the result, and Python drives the info menu. show_info_with_cancel now uses MoreInfoScreen and distinguishes data vs. non-data values for styling. Test flows are updated to use press_yes instead of swipe_up for Delizia. The changes are a UI architecture refactor, not a vulnerability fix.
Changed components
core/embed/rust/src/ui/layout_delizia/component/more_info_screen.rscore/embed/rust/src/ui/layout_delizia/component_msg_obj.rscore/embed/rust/src/ui/layout_delizia/ui_firmware.rscore/embed/rust/src/ui/layout_delizia/flow/request_number.rscore/src/trezor/ui/layouts/delizia/reset.pytests/common.pytests/input_flows.pyInspect captured patch +280 / −241
### core/embed/rust/src/ui/component/map.rs
@@ -13,6 +13,10 @@ impl<T, F> MsgMap<T, F> {
pub fn new(inner: T, func: F) -> Self {
Self { inner, func }
}
+
+ pub fn inner(&self) -> &T {
+ &self.inner
+ }
}
impl<T, F, U> Component for MsgMap<T, F>
### core/embed/rust/src/ui/layout_delizia/component/mod.rs
@@ -24,6 +24,8 @@ mod homescreen;
mod keyboard;
mod loader;
#[cfg(feature = "translations")]
+mod more_info_screen;
+#[cfg(feature = "translations")]
mod number_input;
#[cfg(feature = "translations")]
pub mod number_input_slider;
@@ -40,7 +42,6 @@ mod swipe_up_screen;
#[cfg(feature = "translations")]
mod tap_to_confirm;
mod trade_screen;
-mod updatable_more_info;
mod welcome_screen;
#[cfg(feature = "translations")]
@@ -72,6 +73,8 @@ pub use keyboard::{
};
pub use loader::{Loader, LoaderMsg, LoaderStyle, LoaderStyleSheet};
#[cfg(feature = "translations")]
+pub use more_info_screen::MoreInfoScreen;
+#[cfg(feature = "translations")]
pub use number_input::{NumberInputDialog, NumberInputDialogMsg};
#[cfg(feature = "translations")]
pub use number_input_slider::NumberInputSliderDialog;
@@ -88,7 +91,6 @@ pub use swipe_up_screen::{SwipeUpScreen, SwipeUpScreenMsg};
#[cfg(feature = "translations")]
pub use tap_to_confirm::TapToConfirm;
pub use trade_screen::TradeScreen;
-pub use updatable_more_info::UpdatableMoreInfo;
pub use vertical_menu::{
PagedVerticalMenu, ScrolledVerticalMenu, VerticalMenu, VerticalMenuChoiceMsg, VerticalMenuItem,
VerticalMenuItems,
### core/embed/rust/src/ui/layout_delizia/component/more_info_screen.rs
@@ -0,0 +1,146 @@
+use super::theme::{self, TITLE_HEIGHT};
+use super::{ActionBar, ActionBarMsg, Header};
+use crate::strutil::TString;
+use crate::ui::component::text::paragraphs::{ParagraphSource, Paragraphs};
+use crate::ui::component::{Component, Event, EventCtx, FlowMsg, Paginate};
+use crate::ui::geometry::{Insets, Rect};
+use crate::ui::shape::Renderer;
+use crate::ui::util::Pager;
+
+/// Full-screen component showing paragraphs of text with a close button in
+/// the header. When the text does not fit on a single page, an action bar
+/// with up/down buttons is shown to paginate through it (similar to the
+/// paginate-only action bar in Eckhart's `TextScreen`).
+pub struct MoreInfoScreen<T>
+where
+ T: ParagraphSource<'static>,
+{
+ header: Header,
+ paragraphs: Paragraphs<T>,
+ action_bar: ActionBar,
+ /// Whether the content needs the action bar for pagination. Decided on
+ /// the first `place()` pass and kept stable afterwards, so that
+ /// re-placing after a page change does not move the content around.
+ paginated: Option<bool>,
+}
+
+impl<T> MoreInfoScreen<T>
+where
+ T: ParagraphSource<'static>,
+{
+ pub fn new(title: TString<'static>, paragraphs: Paragraphs<T>) -> Self {
+ Self {
+ header: Header::left_aligned(title).with_cancel_button(),
+ paragraphs,
+ action_bar: ActionBar::new_paginate_only(),
+ paginated: None,
+ }
+ }
+
+ fn update_page(&mut self, ctx: &mut EventCtx, page: u16) {
+ self.change_page(page);
+ self.action_bar.update(ctx, self.pager());
+ ctx.request_paint();
+ }
+}
+
+impl<T> Component for MoreInfoScreen<T>
+where
+ T: ParagraphSource<'static>,
+{
+ type Msg = FlowMsg;
+
+ fn place(&mut self, bounds: Rect) -> Rect {
+ let header_height = self.header.place(bounds).height().max(TITLE_HEIGHT);
+ let rest = bounds.inset(Insets::top(header_height + theme::SPACING));
+
+ let paginated = if let Some(paginated) = self.paginated {
+ paginated
+ } else {
+ // First pass without the action bar to find out whether the
+ // content paginates at all.
+ self.paragraphs.place(rest);
+ let paginated = self.paragraphs.pager().total() > 1;
+ self.paginated = Some(paginated);
+ paginated
+ };
+
+ let content_area = if paginated {
+ let (content_area, bar_area) =
+ rest.split_bottom(ActionBar::ACTION_BAR_HEIGHT + theme::SPACING);
+ let (bar_area, _) = bar_area.split_top(ActionBar::ACTION_BAR_HEIGHT);
+ self.action_bar.place(bar_area);
+ content_area
+ } else {
+ rest
+ };
+ self.paragraphs.place(content_area);
+
+ bounds
+ }
+
+ fn event(&mut self, ctx: &mut EventCtx, event: Event) -> Option<Self::Msg> {
+ // Update page count of the screen
+ ctx.set_page_count(self.pager().total());
+
+ if let Some(msg @ FlowMsg::Cancelled) = self.header.event(ctx, event) {
+ return Some(msg);
+ }
+
+ if self.paginated == Some(true) {
+ match self.action_bar.event(ctx, event) {
+ Some(ActionBarMsg::Prev) => {
+ let page = self.pager().prev();
+ self.update_page(ctx, page);
+ return None;
+ }
+ Some(ActionBarMsg::Next) => {
+ let page = self.pager().next();
+ self.update_page(ctx, page);
+ return None;
+ }
+ None => {}
+ }
+ }
+
+ self.paragraphs.event(ctx, event);
+ None
+ }
+
+ fn render<'s>(&'s self, target: &mut impl Renderer<'s>) {
+ self.header.render(target);
+ self.paragraphs.render(target);
+ if self.paginated == Some(true) {
+ self.action_bar.render(target);
+ }
+ }
+}
+
+impl<T> Paginate for MoreInfoScreen<T>
+where
+ T: ParagraphSource<'static>,
+{
+ fn pager(&self) -> Pager {
+ self.paragraphs.pager()
+ }
+
+ fn change_page(&mut self, active_page: u16) {
+ self.paragraphs.change_page(active_page);
+ }
+}
+
+#[cfg(feature = "ui_debug")]
+impl<T> crate::trace::Trace for MoreInfoScreen<T>
+where
+ T: ParagraphSource<'static>,
+{
+ fn trace(&self, t: &mut dyn crate::trace::Tracer) {
+ t.component("MoreInfoScreen");
+ t.child("header", &self.header);
+ t.child("paragraphs", &self.paragraphs);
+ if self.paginated == Some(true) {
+ t.child("action_bar", &self.action_bar);
+ }
+ t.int("page_count", i64::from(self.pager().total()));
+ }
+}
### core/embed/rust/src/ui/layout_delizia/component/updatable_more_info.rs
@@ -1,72 +0,0 @@
-use super::theme;
-use crate::strutil::TString;
-use crate::ui::component::paginated::SinglePage;
-use crate::ui::component::text::paragraphs::{Paragraph, Paragraphs};
-use crate::ui::component::{Component, Event, EventCtx, Never};
-use crate::ui::geometry::Rect;
-use crate::ui::shape::Renderer;
-
-pub struct UpdatableMoreInfo<F>
-where
- F: Fn() -> TString<'static>,
-{
- info_func: F,
- paragraphs: Paragraphs<Paragraph<'static>>,
-}
-
-impl<F> UpdatableMoreInfo<F>
-where
- F: Fn() -> TString<'static>,
-{
- pub fn new(info_func: F) -> Self {
- Self {
- info_func,
- paragraphs: Paragraphs::new(Paragraph::new(
- &theme::TEXT_MAIN_GREY_LIGHT,
- TString::empty(),
- )),
- }
- }
-
- fn update_text(&mut self, ctx: &mut EventCtx) {
- let text = (self.info_func)();
- self.paragraphs.update(text);
- ctx.request_paint();
- }
-}
-
-impl<F> Component for UpdatableMoreInfo<F>
-where
- F: Fn() -> TString<'static>,
-{
- type Msg = Never;
-
- fn place(&mut self, bounds: Rect) -> Rect {
- self.paragraphs.place(bounds);
- bounds
- }
-
- fn event(&mut self, ctx: &mut EventCtx, event: Event) -> Option<Self::Msg> {
- if let Event::Attach(_) = event {
- self.update_text(ctx);
- }
- None
- }
-
- fn render<'s>(&'s self, target: &mut impl Renderer<'s>) {
- self.paragraphs.render(target);
- }
-}
-
-impl<F: Fn() -> TString<'static>> SinglePage for UpdatableMoreInfo<F> {}
-
-#[cfg(feature = "ui_debug")]
-impl<F> crate::trace::Trace for UpdatableMoreInfo<F>
-where
- F: Fn() -> TString<'static>,
-{
- fn trace(&self, t: &mut dyn crate::trace::Tracer) {
- t.component("UpdatableMoreInfo");
- t.child("paragraphs", &self.paragraphs);
- }
-}
### core/embed/rust/src/ui/layout_delizia/component_msg_obj.rs
@@ -2,9 +2,10 @@ use core::convert::TryInto;
use super::component::{
AddressDetails, CoinJoinProgress, Frame, FrameMsg, Homescreen, HomescreenMsg, Lockscreen,
- MnemonicInput, MnemonicKeyboard, MnemonicKeyboardMsg, PinKeyboard, PinKeyboardMsg, Progress,
- PromptScreen, SelectWordCount, SelectWordCountMsg, StatusScreen, SwipeContent, SwipeUpScreen,
- SwipeUpScreenMsg, VerticalMenu, VerticalMenuChoiceMsg,
+ MnemonicInput, MnemonicKeyboard, MnemonicKeyboardMsg, MoreInfoScreen, NumberInputDialog,
+ NumberInputDialogMsg, PinKeyboard, PinKeyboardMsg, Progress, PromptScreen, SelectWordCount,
+ SelectWordCountMsg, StatusScreen, SwipeContent, SwipeUpScreen, SwipeUpScreenMsg, VerticalMenu,
+ VerticalMenuChoiceMsg,
};
use crate::micropython::gc::GcBox;
use crate::micropython::{Error, Obj};
@@ -14,10 +15,10 @@ use crate::ui::component::{
text::paragraphs::{ParagraphSource, Paragraphs},
Timeout,
};
-use crate::ui::component::{Component, Never};
+use crate::ui::component::{Component, FlowMsg, MsgMap, Never};
use crate::ui::flow::Swipable;
use crate::ui::layout::obj::ComponentMsgObj;
-use crate::ui::layout::result::{CANCELLED, CONFIRMED};
+use crate::ui::layout::result::{CANCELLED, CONFIRMED, INFO};
impl TryFrom<SelectWordCountMsg> for Obj {
type Error = Error;
@@ -135,6 +136,36 @@ where
}
}
+#[cfg(not(feature = "clippy"))]
+impl<T> ComponentMsgObj for MoreInfoScreen<T>
+where
+ T: ParagraphSource<'static>,
+{
+ fn msg_try_into_obj(&self, msg: Self::Msg) -> Result<Obj, Error> {
+ msg.try_into()
+ }
+}
+
+/// Layout returned by `request_number`: the number input screen with the
+/// "more info" menu button in the header.
+pub type RequestNumberScreen = MsgMap<
+ Frame<SwipeContent<NumberInputDialog>>,
+ fn(FrameMsg<NumberInputDialogMsg>) -> Option<FlowMsg>,
+>;
+
+impl ComponentMsgObj for RequestNumberScreen {
+ fn msg_try_into_obj(&self, msg: Self::Msg) -> Result<Obj, Error> {
+ // Return not only the result, but also the currently displayed number,
+ // so that Python can e.g. show the corresponding "more info" text.
+ let value: u16 = self.inner().inner().inner().value();
+ match msg {
+ FlowMsg::Confirmed | FlowMsg::Next => Ok((CONFIRMED.as_obj(), value).try_into()?),
+ FlowMsg::Info => Ok((INFO.as_obj(), value).try_into()?),
+ msg => msg.try_into(),
+ }
+ }
+}
+
impl ComponentMsgObj for Progress {
fn msg_try_into_obj(&self, _msg: Self::Msg) -> Result<Obj, Error> {
unreachable!()
### core/embed/rust/src/ui/layout_delizia/flow/mod.rs
@@ -9,7 +9,6 @@ pub mod confirm_summary;
pub mod continue_recovery_homepage;
pub mod prompt_backup;
pub mod receive;
-pub mod request_number;
pub mod request_passphrase;
pub mod set_brightness;
pub mod show_danger;
@@ -29,7 +28,6 @@ pub use confirm_summary::new_confirm_summary;
pub use continue_recovery_homepage::new_continue_recovery_homepage;
pub use prompt_backup::PromptBackup;
pub use receive::Receive;
-pub use request_number::RequestNumber;
pub use request_passphrase::RequestPassphrase;
pub use set_brightness::SetBrightness;
pub use show_danger::ShowDanger;
### core/embed/rust/src/ui/layout_delizia/flow/request_number.rs
@@ -1,103 +0,0 @@
-use core::sync::atomic::{AtomicU16, Ordering};
-
-use super::super::component::{
- Frame, Header, NumberInputDialog, NumberInputDialogMsg, SwipeContent, UpdatableMoreInfo,
- VerticalMenu,
-};
-use super::super::theme;
-use crate::micropython::Error;
-use crate::strutil::TString;
-use crate::translations::TR;
-use crate::ui::flow::base::{Decision, DecisionBuilder as _};
-use crate::ui::flow::{FlowController, FlowMsg, SwipeFlow};
-use crate::ui::geometry::Direction;
-
-#[derive(Copy, Clone, PartialEq, Eq)]
-pub enum RequestNumber {
- Number,
- Menu,
- Info,
-}
-
-impl FlowController for RequestNumber {
- #[inline]
- fn index(&'static self) -> usize {
- *self as usize
- }
-
- fn handle_swipe(&'static self, direction: Direction) -> Decision {
- match (self, direction) {
- (Self::Number, Direction::Up) => self.return_msg(FlowMsg::Choice(
- NUM_DISPLAYED.load(Ordering::Relaxed).into(),
- )),
- _ => self.do_nothing(),
- }
- }
-
- fn handle_event(&'static self, msg: FlowMsg) -> Decision {
- match (self, msg) {
- (Self::Number, FlowMsg::Info) => Self::Menu.goto(),
- (Self::Menu, FlowMsg::Choice(0)) => Self::Info.swipe_left(),
- (Self::Menu, FlowMsg::Cancelled) => Self::Number.swipe_right(),
- (Self::Info, FlowMsg::Cancelled) => Self::Menu.goto(),
- _ => self.do_nothing(),
- }
- }
-}
-
-static NUM_DISPLAYED: AtomicU16 = AtomicU16::new(0);
-
-#[allow(clippy::too_many_arguments)]
-pub fn new_request_number(
- title: TString<'static>,
- count: u32,
- min_count: u32,
- max_count: u32,
- description: TString<'static>,
- info_closure: impl Fn(u32) -> TString<'static> + 'static,
-) -> Result<SwipeFlow, Error> {
- NUM_DISPLAYED.store(count as u16, Ordering::Relaxed);
-
- // wrap the closure for obtaining MoreInfo text and call it with NUM_DISPLAYED
- let info_closure = move || {
- let curr_number = NUM_DISPLAYED.load(Ordering::Relaxed);
- info_closure(u32::from(curr_number))
- };
-
- let number_input_dialog = NumberInputDialog::new(
- min_count as u16,
- max_count as u16,
- count as u16,
- description,
- )?;
- let content_number_input = Frame::with_header(
- Header::left_aligned(title).with_menu_button(),
- SwipeContent::new(number_input_dialog),
- )
- .with_swipeup_footer(None)
- .map(|msg| match msg {
- NumberInputDialogMsg::Changed(n) => {
- NUM_DISPLAYED.store(n, Ordering::Relaxed);
- None
- }
- });
-
- let content_menu = Frame::with_header(
- Header::left_aligned(TString::empty()).with_cancel_button(),
- VerticalMenu::empty().item(theme::ICON_CHEVRON_RIGHT, TR::buttons__more_info.into()),
- )
- .map(super::util::map_to_choice);
-
- let updatable_info = UpdatableMoreInfo::new(info_closure);
- let content_info = Frame::with_header(
- Header::left_aligned(TString::empty()).with_cancel_button(),
- SwipeContent::new(updatable_info),
- )
- .map_to_button_msg();
-
- let mut res = SwipeFlow::new(&RequestNumber::Number);
- res.add_page(&RequestNumber::Number, content_number_input)?
- .add_page(&RequestNumber::Menu, content_menu)?
- .add_page(&RequestNumber::Info, content_info)?;
- Ok(res)
-}
### core/embed/rust/src/ui/layout_delizia/ui_firmware.rs
@@ -4,9 +4,10 @@ use heapless::Vec;
use super::component::{
check_homescreen_format, Bip39Input, CoinJoinProgress, Frame, FrameMsg, Header, Homescreen,
- Lockscreen, MnemonicKeyboard, PinKeyboard, Progress, PromptScreen, ScrolledVerticalMenu,
- SelectWordCount, SelectWordCountLayout, Slip39Input, StatusScreen, SwipeContent, SwipeUpScreen,
- TradeScreen, VerticalMenu, VerticalMenuChoiceMsg, VerticalMenuItem, VerticalMenuItems,
+ Lockscreen, MnemonicKeyboard, MoreInfoScreen, NumberInputDialog, PinKeyboard, Progress,
+ PromptScreen, ScrolledVerticalMenu, SelectWordCount, SelectWordCountLayout, Slip39Input,
+ StatusScreen, SwipeContent, SwipeUpScreen, TradeScreen, VerticalMenu, VerticalMenuChoiceMsg,
+ VerticalMenuItem, VerticalMenuItems,
};
use super::flow::{
self, new_confirm_action_simple, ConfirmActionExtra, ConfirmActionMenuStrings,
@@ -648,25 +649,31 @@ impl FirmwareUI for UIDelizia {
min_count: u32,
max_count: u32,
description: Option<TString<'static>>,
- more_info_callback: Option<impl Fn(u32) -> TString<'static> + 'static>,
+ _more_info_callback: Option<impl Fn(u32) -> TString<'static> + 'static>,
) -> Result<impl LayoutMaybeTrace, Error> {
debug_assert!(
description.is_some(),
"Description is required for request_number"
);
- debug_assert!(
- more_info_callback.is_some(),
- "More info callback is required for request_number"
+ // The "more info" content is driven from Python: the menu button in
+ // the header emits `FlowMsg::Info` and the layout returns the
+ // currently displayed number along with the result (see
+ // `ComponentMsgObj for RequestNumberScreen`).
+ let layout = RootComponent::new(
+ Frame::with_header(
+ Header::left_aligned(title).with_menu_button(),
+ SwipeContent::new(NumberInputDialog::new(
+ min_count as u16,
+ max_count as u16,
+ count as u16,
+ description.unwrap(),
+ )?),
+ )
+ .with_swipeup_footer(None)
+ .with_external_menu()
+ .map_to_button_msg(),
);
- let flow = flow::request_number::new_request_number(
- title,
- count,
- min_count,
- max_count,
- description.unwrap(),
- more_info_callback.unwrap(),
- )?;
- Ok(flow)
+ Ok(layout)
}
fn request_duration(
@@ -988,21 +995,21 @@ impl FirmwareUI for UIDelizia {
let mut paragraphs = ParagraphVecShort::new();
for para in IterBuf::new().try_iterate(items)? {
- let [key, value, _]: [Obj; 3] = util::iter_into_array(para)?;
+ let [key, value, is_data]: [Obj; 3] = util::iter_into_array(para)?;
let key: TString = key.try_into()?;
let value: TString = value.try_into()?;
+ let is_data: bool = is_data.try_into()?;
paragraphs.add(Paragraph::new(&theme::TEXT_SUB_GREY, key).no_break());
if chunkify {
paragraphs.add(Paragraph::new(&theme::TEXT_MONO_ADDRESS_CHUNKS, value));
- } else {
+ } else if is_data {
paragraphs.add(Paragraph::new(&theme::TEXT_MONO_DATA, value));
+ } else {
+ paragraphs.add(Paragraph::new(&theme::TEXT_MAIN_GREY_LIGHT, value));
}
}
- let layout = RootComponent::new(SwipeUpScreen::new(Frame::with_header(
- Header::left_aligned(title).with_cancel_button(),
- SwipeContent::new(paragraphs.into_paragraphs()),
- )));
+ let layout = RootComponent::new(MoreInfoScreen::new(title, paragraphs.into_paragraphs()));
Ok(layout)
}
### core/src/trezor/ui/layouts/delizia/reset.py
@@ -152,31 +152,56 @@ async def _prompt_number(
max_count: int,
br_name: str,
) -> int:
+ from trezor.ui.layouts.menu import Menu, leaf_from_layout, show_menu
+
+ def info_layout(count: int) -> trezorui_api.LayoutContext[trezorui_api.UiResult]:
+ return trezorui_api.show_info_with_cancel(
+ title="",
+ items=[("", info(count), False)],
+ )
+
with trezorui_api.request_number(
title=title,
count=count,
min_count=min_count,
max_count=max_count,
description=description,
- more_info_callback=info,
- ) as layout:
- result = await interact(
- layout,
- br_name,
- ButtonRequestType.ResetDevice,
- raise_on_cancel=None,
- )
-
- if __debug__ and result is CONFIRMED:
- # sent by debuglink. debuglink does not change the number of shares anyway
- # so use the initial one
- return count
-
- if result is not trezorui_api.CANCELLED:
- assert isinstance(result, int)
- return result
- else:
- raise ActionCancelled # user cancelled request number prompt
+ ) as num_input:
+ br_name_once: str | None = br_name
+ while True:
+ result = await interact(
+ num_input,
+ br_name_once,
+ ButtonRequestType.ResetDevice,
+ raise_on_cancel=None,
+ )
+ br_name_once = None # ButtonRequest should be sent only once
+
+ if result is trezorui_api.CANCELLED:
+ raise ActionCancelled # user cancelled request number prompt
+
+ if __debug__ and not isinstance(result, tuple):
+ # sent by debuglink. debuglink does not change the number of
+ # shares anyway so use the initial one
+ result = (result, count)
+ status, value = result
+
+ if status is CONFIRMED:
+ assert isinstance(value, int)
+ return value
+
+ if status is trezorui_api.INFO:
+ # shows the menu with the "more info" screen
+ menu = Menu(
+ [
+ leaf_from_layout(
+ TR.buttons__more_info, lambda: info_layout(value)
+ )
+ ]
+ )
+ await show_menu(menu)
+ else:
+ raise RuntimeError
def slip39_prompt_threshold(
### tests/common.py
@@ -351,7 +351,8 @@ def click_info_button_delizia_eckhart(debug: "DebugLink"):
"""Click Shamir backup info button, scroll through it and return back."""
debug.click(debug.screen_buttons.menu())
layout = debug.read_layout()
- assert "VerticalMenu" in layout.all_components()
+ components = layout.all_components()
+ assert "VerticalMenu" in components or "ScrolledVerticalMenu" in components
# Click on the first item in the vertical menu
debug.button_actions.navigate_to_menu_item(0)
### tests/input_flows.py
@@ -2131,23 +2131,23 @@ def input_flow_delizia(self) -> BRGeneratorType:
self.debug.press_yes()
assert (yield).name == "backup_intro"
- self.debug.swipe_up()
+ self.debug.press_yes()
assert (yield).name == "slip39_checklist"
- self.debug.swipe_up()
+ self.debug.press_yes()
assert (yield).name == "slip39_shares"
if self.click_info:
click_info_button_delizia_eckhart(self.debug)
- self.debug.swipe_up()
+ self.debug.press_yes()
assert (yield).name == "slip39_checklist"
- self.debug.swipe_up()
+ self.debug.press_yes()
assert (yield).name == "slip39_threshold"
if self.click_info:
click_info_button_delizia_eckhart(self.debug)
- self.debug.swipe_up()
+ self.debug.press_yes()
assert (yield).name == "slip39_checklist"
- self.debug.swipe_up()
+ self.debug.press_yes()
assert (yield).name == "backup_warning"
- self.debug.swipe_up()
+ self.debug.press_yes()
# Mnemonic phrases
self.mnemonics = yield from load_N_shares(self.debug, 5)
@@ -2538,30 +2538,30 @@ def input_flow_caesar(self) -> BRGeneratorType:
def input_flow_delizia(self) -> BRGeneratorType:
assert self.backup_method is messages.BackupMethod.Display
assert (yield).name == "backup_intro"
- self.debug.swipe_up()
+ self.debug.press_yes()
assert (yield).name == "slip39_checklist"
- self.debug.swipe_up()
+ self.debug.press_yes()
assert (yield).name == "slip39_groups"
if self.click_info:
click_info_button_delizia_eckhart(self.debug)
- self.debug.swipe_up()
+ self.debug.press_yes()
assert (yield).name == "slip39_checklist"
- self.debug.swipe_up()
+ self.debug.press_yes()
assert (yield).name == "slip39_group_threshold"
if self.click_info:
click_info_button_delizia_eckhart(self.debug)
- self.debug.swipe_up()
+ self.debug.press_yes()
assert (yield).name == "slip39_checklist"
- self.debug.swipe_up()
+ self.debug.press_yes()
for _i in range(5): # for each of 5 groups
assert (yield).name == "slip39_shares"
if self.click_info:
click_info_button_delizia_eckhart(self.debug)
- self.debug.swipe_up()
+ self.debug.press_yes()
assert (yield).name == "slip39_threshold"
if self.click_info:
click_info_button_delizia_eckhart(self.debug)
- self.debug.swipe_up()
+ self.debug.press_yes()
assert (yield).name == "backup_warning"
self.debug.press_yes()
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.