chore(core/bolt): drop Bolt icon image if title is unset
What changed, and why it matters
This is a small user-interface cleanup in the Trezor hardware wallet firmware. It lets certain on-screen confirmation dialogs omit a large icon and title area when no title is provided, making the layout more flexible. There is no direct evidence in the commit that this fixes a security vulnerability.
No security action required. Treat as a normal UI refactor during code review.
Security signals we found
No security-relevant signals observed in the diff.
Change is purely UI layout: optional icon/title rendering.
No memory-safety, cryptographic, authentication, or authorization changes.
Evidence from the diff
The commit changes IconDialog in the Bolt UI layout so its image field is Option<Child<BlendedImage>> and its title argument is Option<impl Into<TString>>. When the title is None, the icon image is not placed or rendered, and the paragraphs list is empty. Call sites in ui_firmware.rs are updated to wrap existing titles in Some(...), and one call site now passes the raw Option<TString> title through instead of defaulting to an empty string. This is a layout/behavior refactor with no obvious security-relevant change.
Changed components
core/embed/rust/src/ui/layout_bolt/component/dialog.rscore/embed/rust/src/ui/layout_bolt/ui_firmware.rsInspect captured patch +36 / −21
diff --git a/core/embed/rust/src/ui/layout_bolt/component/dialog.rs b/core/embed/rust/src/ui/layout_bolt/component/dialog.rs
index f9020cc2..77409553 100644
--- a/core/embed/rust/src/ui/layout_bolt/component/dialog.rs
+++ b/core/embed/rust/src/ui/layout_bolt/component/dialog.rs
@@ -88,7 +88,7 @@ where
}
pub struct IconDialog<U> {
- image: Child<BlendedImage>,
+ image: Option<Child<BlendedImage>>,
paragraphs: Paragraphs<ParagraphVecShort<'static>>,
controls: Child<U>,
}
@@ -97,15 +97,21 @@ impl<U> IconDialog<U>
where
U: Component,
{
- pub fn new(icon: BlendedImage, title: impl Into<TString<'static>>, controls: U) -> Self {
+ pub fn new(
+ icon: BlendedImage,
+ title: Option<impl Into<TString<'static>>>,
+ controls: U,
+ ) -> Self {
+ let (image, paragraphs) = if let Some(title) = title {
+ let title = Paragraph::new(&theme::TEXT_DEMIBOLD, title).centered();
+ let para = ParagraphVecShort::from_iter([title]);
+ (Some(Child::new(icon)), para)
+ } else {
+ (None, ParagraphVecShort::new())
+ };
Self {
- image: Child::new(icon),
- paragraphs: Paragraphs::new(ParagraphVecShort::from_iter([Paragraph::new(
- &theme::TEXT_DEMIBOLD,
- title,
- )
- .centered()]))
- .with_placement(
+ image,
+ paragraphs: Paragraphs::new(paragraphs).with_placement(
LinearPlacement::vertical()
.align_at_center()
.with_spacing(Self::VALUE_SPACE),
@@ -138,13 +144,13 @@ where
pub fn new_shares(lines: [impl Into<TString<'static>>; 4], controls: U) -> Self {
let [l0, l1, l2, l3] = lines;
Self {
- image: Child::new(BlendedImage::new(
+ image: Some(Child::new(BlendedImage::new(
theme::IMAGE_BG_CIRCLE,
theme::IMAGE_FG_SUCCESS,
theme::SUCCESS_COLOR,
theme::FG,
theme::BG,
- )),
+ ))),
paragraphs: ParagraphVecShort::from_iter([
Paragraph::new(&theme::TEXT_NORMAL_OFF_WHITE, l0).centered(),
Paragraph::new(&theme::TEXT_DEMIBOLD, l1).centered(),
@@ -176,9 +182,14 @@ where
let controls_area = self.controls.place(bounds);
let content_area = bounds.inset(Insets::bottom(controls_area.height()));
- let (image_area, content_area) = content_area.split_top(Self::ICON_AREA_HEIGHT);
+ let content_area = if let Some(image) = &mut self.image {
+ let (image_area, content_area) = content_area.split_top(Self::ICON_AREA_HEIGHT);
+ image.place(image_area);
+ content_area
+ } else {
+ content_area
+ };
- self.image.place(image_area);
self.paragraphs.place(content_area);
bounds
}
@@ -189,7 +200,9 @@ where
}
fn render<'s>(&'s self, target: &mut impl Renderer<'s>) {
- self.image.render(target);
+ if let Some(image) = &self.image {
+ image.render(target);
+ }
self.paragraphs.render(target);
self.controls.render(target);
}
@@ -202,7 +215,9 @@ where
{
fn trace(&self, t: &mut dyn crate::trace::Tracer) {
t.component("IconDialog");
- t.child("image", &self.image);
+ if let Some(image) = &self.image {
+ t.child("image", image);
+ }
t.child("content", &self.paragraphs);
t.child("controls", &self.controls);
}
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 021bb180..3d6be027 100644
--- a/core/embed/rust/src/ui/layout_bolt/ui_firmware.rs
+++ b/core/embed/rust/src/ui/layout_bolt/ui_firmware.rs
@@ -881,7 +881,7 @@ impl FirmwareUI for UIBolt {
theme::BG,
);
new_show_modal(
- title,
+ Some(title),
TString::empty(),
description,
(!button.is_empty()).then_some(button),
@@ -1015,7 +1015,7 @@ impl FirmwareUI for UIBolt {
theme::BG,
);
new_show_modal(
- title,
+ Some(title),
TString::empty(),
description,
button_text,
@@ -1089,7 +1089,7 @@ impl FirmwareUI for UIBolt {
let layout = RootComponent::new(
IconDialog::new(
icon,
- title,
+ Some(title),
Button::cancel_confirm(
Button::with_icon(theme::ICON_BACK),
Button::with_text(button).styled(theme::button_reset()),
@@ -1253,7 +1253,7 @@ impl FirmwareUI for UIBolt {
theme::BG,
);
new_show_modal(
- title,
+ Some(title),
TString::empty(),
description,
(!button.is_empty()).then_some(button),
@@ -1290,7 +1290,7 @@ impl FirmwareUI for UIBolt {
return Err(Error::ValueError(c"Non-empty title is required"));
}
new_show_modal(
- title.unwrap_or(TString::empty()),
+ title,
value,
description,
(!button.is_empty()).then_some(button),
@@ -1312,7 +1312,7 @@ impl FirmwareUI for UIBolt {
#[allow(clippy::too_many_arguments)]
fn new_show_modal(
- title: TString<'static>,
+ title: Option<TString<'static>>,
value: TString<'static>,
description: TString<'static>,
button: Option<TString<'static>>,
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.