chore(core/eckhart): update device menu connections settings
What changed, and why it matters
This commit updates the on-screen device menu for a new Trezor hardware wallet design (codename Eckhart). It adds Bluetooth pairing controls such as 'Connect', 'Forget', and 'Forget all', and wires some of those buttons to existing unpair/disconnect routines. The changes are almost entirely user-interface code; there is no obvious security bug, but several TODO comments show the underlying Bluetooth actions are still incomplete or mocked.
Treat as a routine UI feature commit. Reviewers should verify that the final Bluetooth backend implementations honor the confirmation prompts, enforce authorization (e.g., PIN/passphrase), and validate device indices before acting on DeviceConnect/DeviceUnpair. The mock data and TODOs should be resolved before release.
Security signals we found
New UI actions for Bluetooth connect/disconnect/unpair are wired to user-facing buttons
Several handler branches contain TODO comments and placeholder/mock data for Bluetooth state
Existing unpair(BleUnpair(all=True)) is now reachable from a single 'Forget all' menu item
Confirmation prompts are added before connect/disconnect/unpair actions
No input validation or authorization changes visible in the diff
Evidence from the diff
The patch refactors the Eckhart layout’s device_menu_screen.rs and the MicroPython homescreen handler device_menu.py. It introduces new translation keys (ble__forget_all, words__connect, words__forget), restructures the paired-devices submenu, and propagates a connected_idx parameter so the UI can show which paired device is currently connected. It also adds handler branches for DeviceDisconnect, DevicePair, DeviceUnpairAll, DeviceConnect, and DeviceUnpair, some of which call existing unpair() or show confirmation prompts. A FIXME/TODO note indicates the paired-device list and connection state are still mock data.
Changed components
core/embed/rust/src/ui/layout_eckhart/firmware/device_menu_screen.rscore/src/apps/homescreen/device_menu.pycore/embed/rust/src/ui/layout_eckhart/component/button.rscore/translations/en.jsoncore/translations/order.jsoncore/translations/signatures.jsoncore/mocks/trezortranslate_keys.pyicore/embed/rust/librust_qstr.hcore/embed/rust/src/translations/generated/translated_string.rsInspect captured patch +170 / −70
diff --git a/core/embed/rust/librust_qstr.h b/core/embed/rust/librust_qstr.h
index 5c8987c8..0b595f3b 100644
--- a/core/embed/rust/librust_qstr.h
+++ b/core/embed/rust/librust_qstr.h
@@ -174,6 +174,7 @@ static void _librust_qstrs(void) {
MP_QSTR_bitcoin__voting_rights;
MP_QSTR_ble__disable;
MP_QSTR_ble__enable;
+ MP_QSTR_ble__forget_all;
MP_QSTR_ble__manage_paired;
MP_QSTR_ble__pair_new;
MP_QSTR_ble__pair_title;
@@ -930,6 +931,7 @@ static void _librust_qstrs(void) {
MP_QSTR_words__chain;
MP_QSTR_words__confirm;
MP_QSTR_words__confirm_fee;
+ MP_QSTR_words__connect;
MP_QSTR_words__connected;
MP_QSTR_words__contains;
MP_QSTR_words__continue_anyway;
@@ -942,6 +944,7 @@ static void _librust_qstrs(void) {
MP_QSTR_words__enabled;
MP_QSTR_words__error;
MP_QSTR_words__fee;
+ MP_QSTR_words__forget;
MP_QSTR_words__from;
MP_QSTR_words__good_to_know;
MP_QSTR_words__important;
diff --git a/core/embed/rust/src/translations/generated/translated_string.rs b/core/embed/rust/src/translations/generated/translated_string.rs
index 20d92b73..25ea1cc8 100644
--- a/core/embed/rust/src/translations/generated/translated_string.rs
+++ b/core/embed/rust/src/translations/generated/translated_string.rs
@@ -1512,6 +1512,9 @@ pub enum TranslatedString {
lockscreen__unlock = 1121, // "Unlock"
recovery__start_entering = 1122, // "Start entering"
words__disconnected = 1123, // "Disconnected"
+ ble__forget_all = 1124, // "Forget all"
+ words__connect = 1125, // "Connect"
+ words__forget = 1126, // "Forget"
}
impl TranslatedString {
@@ -3350,6 +3353,9 @@ impl TranslatedString {
(Self::lockscreen__unlock, "Unlock"),
(Self::recovery__start_entering, "Start entering"),
(Self::words__disconnected, "Disconnected"),
+ (Self::ble__forget_all, "Forget all"),
+ (Self::words__connect, "Connect"),
+ (Self::words__forget, "Forget"),
];
#[cfg(feature = "micropython")]
@@ -3429,6 +3435,7 @@ impl TranslatedString {
(Qstr::MP_QSTR_bitcoin__voting_rights, Self::bitcoin__voting_rights),
(Qstr::MP_QSTR_ble__disable, Self::ble__disable),
(Qstr::MP_QSTR_ble__enable, Self::ble__enable),
+ (Qstr::MP_QSTR_ble__forget_all, Self::ble__forget_all),
(Qstr::MP_QSTR_ble__manage_paired, Self::ble__manage_paired),
(Qstr::MP_QSTR_ble__pair_new, Self::ble__pair_new),
(Qstr::MP_QSTR_ble__pair_title, Self::ble__pair_title),
@@ -4790,6 +4797,7 @@ impl TranslatedString {
(Qstr::MP_QSTR_words__chain, Self::words__chain),
(Qstr::MP_QSTR_words__confirm, Self::words__confirm),
(Qstr::MP_QSTR_words__confirm_fee, Self::words__confirm_fee),
+ (Qstr::MP_QSTR_words__connect, Self::words__connect),
(Qstr::MP_QSTR_words__connected, Self::words__connected),
(Qstr::MP_QSTR_words__contains, Self::words__contains),
(Qstr::MP_QSTR_words__continue_anyway, Self::words__continue_anyway),
@@ -4802,6 +4810,7 @@ impl TranslatedString {
(Qstr::MP_QSTR_words__enabled, Self::words__enabled),
(Qstr::MP_QSTR_words__error, Self::words__error),
(Qstr::MP_QSTR_words__fee, Self::words__fee),
+ (Qstr::MP_QSTR_words__forget, Self::words__forget),
(Qstr::MP_QSTR_words__from, Self::words__from),
(Qstr::MP_QSTR_words__good_to_know, Self::words__good_to_know),
(Qstr::MP_QSTR_words__important, Self::words__important),
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 fa63c2ad..2a5d44e2 100644
--- a/core/embed/rust/src/ui/layout_eckhart/component/button.rs
+++ b/core/embed/rust/src/ui/layout_eckhart/component/button.rs
@@ -122,15 +122,13 @@ impl Button {
&theme::TEXT_MENU_ITEM_SUBTITLE,
)
};
- let subtext = if let Some(subtext) = subtext {
- subtext
- } else {
+ let subtext = subtext.unwrap_or_else(|| {
if connected {
TR::words__connected.into()
} else {
TR::words__disconnected.into()
}
- };
+ });
Self::with_text_and_subtext(text, subtext, subtext_style, Some(icon))
.with_text_align(Self::MENU_ITEM_ALIGNMENT)
diff --git a/core/embed/rust/src/ui/layout_eckhart/firmware/device_menu_screen.rs b/core/embed/rust/src/ui/layout_eckhart/firmware/device_menu_screen.rs
index 3e37638b..0ba6959d 100644
--- a/core/embed/rust/src/ui/layout_eckhart/firmware/device_menu_screen.rs
+++ b/core/embed/rust/src/ui/layout_eckhart/firmware/device_menu_screen.rs
@@ -40,7 +40,7 @@ use heapless::Vec;
// - pin code
// - wipe code
// - device
-const MAX_SUBMENUS: usize = 8; // TODO: decrease to 7 BLE implementation
+const MAX_SUBMENUS: usize = 7;
const MAX_DEPTH: usize = 3;
// submenus, device screens, regulatory and about screens
const MAX_SUBSCREENS: usize = MAX_SUBMENUS + MAX_PAIRED_DEVICES + 2;
@@ -97,9 +97,10 @@ struct MenuItem {
text: TString<'static>,
subtext: Option<(TString<'static>, Option<&'static TextStyle>)>,
stylesheet: &'static ButtonStyleSheet,
+ connection_status: Option<bool>,
action: Option<Action>,
}
-const MENU_ITEM_TITLE_STYLE_SHEET: &ButtonStyleSheet = &theme::menu_item_title();
+const MENU_ITEM_NORMAL: &ButtonStyleSheet = &theme::menu_item_title();
const MENU_ITEM_LIGHT_WARNING: &ButtonStyleSheet = &theme::menu_item_title_yellow();
const MENU_ITEM_WARNING: &ButtonStyleSheet = &theme::menu_item_title_orange();
const MENU_ITEM_ERROR: &ButtonStyleSheet = &theme::menu_item_title_red();
@@ -109,8 +110,9 @@ impl MenuItem {
Self {
text,
subtext: None,
- stylesheet: MENU_ITEM_TITLE_STYLE_SHEET,
+ stylesheet: MENU_ITEM_NORMAL,
action,
+ connection_status: None,
}
}
@@ -122,6 +124,11 @@ impl MenuItem {
self
}
+ pub fn with_connection_status(&mut self, connection_status: Option<bool>) -> &mut Self {
+ self.connection_status = connection_status;
+ self
+ }
+
pub fn with_stylesheet(&mut self, stylesheet: &'static ButtonStyleSheet) -> &mut Self {
self.stylesheet = stylesheet;
self
@@ -155,6 +162,7 @@ enum Subscreen {
// A screen allowing the user to to disconnect a device
DeviceScreen(
TString<'static>, /* device name */
+ bool, /* is the device connected? */
usize, /* index in the list of devices */
),
@@ -197,7 +205,7 @@ impl DeviceMenuScreen {
pub fn new(
failed_backup: bool,
paired_devices: Vec<TString<'static>, MAX_PAIRED_DEVICES>,
- _connected_idx: Option<usize>,
+ connected_idx: Option<usize>,
bluetooth: Option<bool>,
pin_code: Option<bool>,
auto_lock_delay: Option<TString<'static>>,
@@ -240,18 +248,19 @@ impl DeviceMenuScreen {
);
let settings = screen.add_settings_menu(bluetooth, security, device);
- let is_connected = !paired_devices.is_empty(); // FIXME after BLE API has this
+ let is_connected = connected_idx.is_some_and(|idx| idx < paired_devices.len());
let connected_subtext: Option<TString<'static>> =
is_connected.then_some("1 device connected".into());
- let mut paired_device_indices: Vec<usize, MAX_PAIRED_DEVICES> = Vec::new();
+ let mut submenu_indices: Vec<usize, MAX_PAIRED_DEVICES> = Vec::new();
for (i, device) in paired_devices.iter().enumerate() {
- unwrap!(paired_device_indices
- .push(screen.add_subscreen(Subscreen::DeviceScreen(*device, i))));
+ let connected = connected_idx == Some(i);
+ unwrap!(submenu_indices
+ .push(screen.add_subscreen(Subscreen::DeviceScreen(*device, connected, i))));
}
- let devices = screen.add_paired_devices_menu(paired_devices, paired_device_indices);
- let pair_and_connect = screen.add_pair_and_connect_menu(devices, connected_subtext);
+ let pair_and_connect =
+ screen.add_pair_and_connect_menu(paired_devices, submenu_indices, connected_idx);
let pin_unset = pin_code == Some(false);
let root = screen.add_root_menu(
failed_backup,
@@ -266,44 +275,36 @@ impl DeviceMenuScreen {
Ok(screen)
}
- fn add_paired_devices_menu(
+ fn add_pair_and_connect_menu(
&mut self,
paired_devices: Vec<TString<'static>, MAX_PAIRED_DEVICES>,
- paired_device_indices: Vec<usize, MAX_PAIRED_DEVICES>,
+ submenu_indices: Vec<usize, MAX_PAIRED_DEVICES>,
+ connected_idx: Option<usize>,
) -> usize {
let mut items: Vec<MenuItem, MEDIUM_MENU_ITEMS> = Vec::new();
- for (device, idx) in paired_devices.iter().zip(paired_device_indices) {
- let mut item_device = MenuItem::new(*device, Some(Action::GoTo(idx)));
+ for ((device_idx, device), submenu_idx) in
+ paired_devices.iter().enumerate().zip(submenu_indices)
+ {
+ let mut item_device = MenuItem::new(*device, Some(Action::GoTo(submenu_idx)));
// TODO: this should be a boolean feature of the device
- item_device.with_subtext(Some((
- TR::words__connected.into(),
- Some(&theme::TEXT_MENU_ITEM_SUBTITLE_GREEN),
- )));
+ let connection_status = match connected_idx {
+ Some(idx) if idx == device_idx => Some(true),
+ _ => Some(false),
+ };
+ item_device.with_connection_status(connection_status);
unwrap!(items.push(item_device));
}
- let submenu_index = self.add_submenu(Submenu::new(items));
- self.add_subscreen(Subscreen::Submenu(submenu_index))
- }
-
- fn add_pair_and_connect_menu(
- &mut self,
- manage_devices_index: usize,
- connected_subtext: Option<TString<'static>>,
- ) -> usize {
- let mut items: Vec<MenuItem, MEDIUM_MENU_ITEMS> = Vec::new();
- let mut manage_paired_item = MenuItem::new(
- TR::ble__manage_paired.into(),
- Some(Action::GoTo(manage_devices_index)),
- );
- manage_paired_item.with_subtext(
- connected_subtext.map(|t| (t, Some(&theme::TEXT_MENU_ITEM_SUBTITLE_GREEN))),
- );
- unwrap!(items.push(manage_paired_item));
unwrap!(items.push(MenuItem::new(
TR::ble__pair_new.into(),
Some(Action::Return(DeviceMenuMsg::DevicePair)),
)));
+ let mut unpair_all_item = MenuItem::new(
+ TR::ble__forget_all.into(),
+ Some(Action::Return(DeviceMenuMsg::DeviceUnpairAll)),
+ );
+ unpair_all_item.with_stylesheet(MENU_ITEM_WARNING);
+ unwrap!(items.push(unpair_all_item));
let submenu_index = self.add_submenu(Submenu::new(items));
self.add_subscreen(Subscreen::Submenu(submenu_index))
@@ -593,7 +594,14 @@ impl DeviceMenuScreen {
let submenu = &self.submenus[*submenu_index];
let mut menu = VerticalMenu::<MediumMenuVec>::empty();
for item in &submenu.items {
- let button = if let Some((subtext, subtext_style)) = item.subtext {
+ let button = if let Some(connected) = item.connection_status {
+ Button::new_connection_item(
+ item.text,
+ *item.stylesheet,
+ item.subtext.map(|(t, _)| t),
+ connected,
+ )
+ } else if let Some((subtext, subtext_style)) = item.subtext {
let subtext_style =
subtext_style.unwrap_or(&theme::TEXT_MENU_ITEM_SUBTITLE);
Button::new_menu_item_with_subtext(
@@ -605,6 +613,7 @@ impl DeviceMenuScreen {
} else {
Button::new_menu_item(item.text, *item.stylesheet)
};
+
menu.item(button);
}
let mut header = Header::new(TR::buttons__back.into()).with_close_button();
@@ -619,22 +628,29 @@ impl DeviceMenuScreen {
*self.active_screen.deref_mut() =
ActiveScreen::Menu(VerticalMenuScreen::new(menu).with_header(header));
}
- Subscreen::DeviceScreen(device, _) => {
+ Subscreen::DeviceScreen(device, connected, _) => {
let mut menu = VerticalMenu::empty();
- menu.item(Button::new_menu_item(device, theme::menu_item_title()));
+ let text = if connected {
+ TR::words__disconnect
+ } else {
+ TR::words__connect
+ };
+ menu.item(Button::new_menu_item(text.into(), theme::menu_item_title()));
menu.item(Button::new_menu_item(
- TR::words__disconnect.into(),
- theme::menu_item_title_red(),
+ TR::words__forget.into(),
+ theme::menu_item_title_orange(),
));
*self.active_screen.deref_mut() = ActiveScreen::Menu(
- VerticalMenuScreen::new(menu).with_header(
- Header::new(TR::words__manage.into())
- .with_close_button()
- .with_left_button(
- Button::with_icon(theme::ICON_CHEVRON_LEFT),
- HeaderMsg::Back,
- ),
- ),
+ VerticalMenuScreen::new(menu)
+ .with_header(
+ Header::new(TR::buttons__back.into())
+ .with_close_button()
+ .with_left_button(
+ Button::with_icon(theme::ICON_CHEVRON_LEFT),
+ HeaderMsg::Back,
+ ),
+ )
+ .with_subtitle(device),
);
}
Subscreen::AboutScreen => {
@@ -644,7 +660,7 @@ impl DeviceMenuScreen {
self.about_items,
&theme::TEXT_SMALL_LIGHT,
&theme::TEXT_MONO_MEDIUM_LIGHT,
- &theme::TEXT_MONO_MEDIUM_LIGHT,
+ &theme::TEXT_MONO_MEDIUM_LIGHT_DATA,
theme::PROP_INNER_SPACING,
theme::PROPS_SPACING,
)
@@ -732,13 +748,22 @@ impl Component for DeviceMenuScreen {
match (subscreen, self.active_screen.deref_mut()) {
(Subscreen::Submenu(..) | Subscreen::DeviceScreen(..), ActiveScreen::Menu(menu)) => {
match menu.event(ctx, event) {
- Some(VerticalMenuScreenMsg::Selected(index)) => {
- if let Subscreen::DeviceScreen(_, _) = subscreen {
- if index == DIS_CONNECT_DEVICE_MENU_INDEX {
- return Some(DeviceMenuMsg::DeviceDisconnect);
+ Some(VerticalMenuScreenMsg::Selected(button_idx)) => {
+ if let Subscreen::DeviceScreen(_, connected, device_idx) = subscreen {
+ match button_idx {
+ DIS_CONNECT_DEVICE_MENU_INDEX if *connected => {
+ return Some(DeviceMenuMsg::DeviceDisconnect);
+ }
+ DIS_CONNECT_DEVICE_MENU_INDEX if !*connected => {
+ return Some(DeviceMenuMsg::DeviceConnect(*device_idx));
+ }
+ FORGET_DEVICE_MENU_INDEX => {
+ return Some(DeviceMenuMsg::DeviceUnpair(*device_idx));
+ }
+ _ => {}
}
} else {
- return self.handle_submenu(ctx, index);
+ return self.handle_submenu(ctx, button_idx);
}
}
Some(VerticalMenuScreenMsg::Back) => {
@@ -780,5 +805,20 @@ impl Component for DeviceMenuScreen {
impl crate::trace::Trace for DeviceMenuScreen {
fn trace(&self, t: &mut dyn crate::trace::Tracer) {
t.component("DeviceMenuScreen");
+
+ match self.active_screen.deref() {
+ ActiveScreen::Menu(ref screen) => {
+ t.child("Menu", screen);
+ }
+ ActiveScreen::About(ref screen) => {
+ t.child("About", screen);
+ }
+ ActiveScreen::Regulatory(ref screen) => {
+ t.child("Regulatory", screen);
+ }
+ ActiveScreen::Empty => {
+ t.null("ActiveScreen");
+ }
+ }
}
}
diff --git a/core/mocks/trezortranslate_keys.pyi b/core/mocks/trezortranslate_keys.pyi
index b42b4b1f..4e15fdbb 100644
--- a/core/mocks/trezortranslate_keys.pyi
+++ b/core/mocks/trezortranslate_keys.pyi
@@ -77,6 +77,7 @@ class TR:
bitcoin__voting_rights: str = "Voting rights to"
ble__disable: str = "Turn Bluetooth off?"
ble__enable: str = "Turn Bluetooth on?"
+ ble__forget_all: str = "Forget all"
ble__manage_paired: str = "Manage paired devices"
ble__pair_new: str = "Pair new device"
ble__pair_title: str = "Pair & connect"
@@ -1020,6 +1021,7 @@ class TR:
words__chain: str = "Chain"
words__confirm: str = "Confirm"
words__confirm_fee: str = "Confirm fee"
+ words__connect: str = "Connect"
words__connected: str = "Connected"
words__contains: str = "Contains"
words__continue_anyway: str = "Continue anyway"
@@ -1032,6 +1034,7 @@ class TR:
words__enabled: str = "Enabled"
words__error: str = "Error"
words__fee: str = "Fee"
+ words__forget: str = "Forget"
words__from: str = "from"
words__good_to_know: str = "Good to know"
words__important: str = "Important"
diff --git a/core/src/apps/homescreen/device_menu.py b/core/src/apps/homescreen/device_menu.py
index a4070790..79e6f04d 100644
--- a/core/src/apps/homescreen/device_menu.py
+++ b/core/src/apps/homescreen/device_menu.py
@@ -5,6 +5,8 @@ from trezor import TR, config, log, utils
from trezor.ui.layouts import interact
from trezorui_api import DeviceMenuResult
+MAX_PAIRED_DEVICES = 4
+
async def handle_device_menu() -> None:
from trezor import strings
@@ -14,7 +16,13 @@ async def handle_device_menu() -> None:
haptic_configurable = is_initialized and utils.USE_HAPTIC
failed_backup = is_initialized and storage_device.unfinished_backup()
# MOCK DATA
- paired_devices = ["Trezor Suite"] if ble.is_connected() else []
+ paired_devices = [
+ "Trezor Suite",
+ "2nd device iiiiiiiiiiiii",
+ "3rd Device iiiiiiiiiiiiii",
+ "4th Device forcedmultiline",
+ ]
+ connected_idx = 1
bluetooth_version = "2.3.1.1"
# ###
firmware_version = ".".join(map(str, utils.VERSION))
@@ -36,7 +44,7 @@ async def handle_device_menu() -> None:
trezorui_api.show_device_menu(
failed_backup=failed_backup,
paired_devices=paired_devices,
- connected_idx=None,
+ connected_idx=connected_idx,
bluetooth=True, # TODO implement bluetooth handling
pin_code=config.has_pin() if is_initialized else None,
auto_lock_delay=auto_lock_delay,
@@ -80,20 +88,53 @@ async def handle_device_menu() -> None:
await wipe_device(WipeDevice())
# Pair & Connect
elif menu_result is DeviceMenuResult.DeviceDisconnect:
- pass # TODO implement device disconnect handling
+ from trezor.ui.layouts import confirm_action
+
+ await confirm_action(
+ "device_disconnect",
+ "device_disconnect",
+ "disconnect currently connected device?",
+ )
+ # TODO implement device disconnect handling
elif menu_result is DeviceMenuResult.DevicePair:
+ from trezor.ui.layouts import show_warning
+
from apps.management.ble.pair_new_device import pair_new_device
- await pair_new_device()
+ if len(paired_devices) < MAX_PAIRED_DEVICES:
+ await pair_new_device()
+ else:
+ await show_warning(
+ "device_pair",
+ "Limit of paired devices reached.",
+ button=TR.buttons__continue,
+ )
elif menu_result is DeviceMenuResult.DeviceUnpairAll:
- pass # TODO implement all devices unpair handling
+ from trezor.messages import BleUnpair
+
+ from apps.management.ble.unpair import unpair
+
+ await unpair(BleUnpair(all=True))
elif isinstance(menu_result, tuple):
+ from trezor.ui.layouts import confirm_action
+
# It's a tuple with (result_type, index)
result_type, index = menu_result
if result_type is DeviceMenuResult.DeviceConnect:
- pass # TODO implement device connect handling
+ await confirm_action(
+ "device_connect",
+ "device_connect",
+ f"connect {index} device?",
+ "The currently connected device will be disconnected.",
+ )
+ # TODO implement device connect handling
elif result_type is DeviceMenuResult.DeviceUnpair:
- pass # TODO implement device unpair handling
+ await confirm_action(
+ "device_unpair",
+ "device_unpair",
+ f"unpair {index} device?",
+ )
+ # TODO implement device unpair handling
else:
raise RuntimeError(f"Unknown menu {result_type}, {index}")
# Bluetooth
diff --git a/core/translations/en.json b/core/translations/en.json
index c6a1813d..b9b91a3a 100644
--- a/core/translations/en.json
+++ b/core/translations/en.json
@@ -109,6 +109,7 @@
"bitcoin__voting_rights": "Voting rights to",
"ble__disable": "Turn Bluetooth off?",
"ble__enable": "Turn Bluetooth on?",
+ "ble__forget_all": "Forget all",
"ble__manage_paired": "Manage paired devices",
"ble__pair_new": "Pair new device",
"ble__pair_title": "Pair & connect",
@@ -1242,6 +1243,7 @@
"words__chain": "Chain",
"words__confirm": "Confirm",
"words__confirm_fee": "Confirm fee",
+ "words__connect": "Connect",
"words__connected": "Connected",
"words__contains": "Contains",
"words__continue_anyway": "Continue anyway",
@@ -1254,6 +1256,7 @@
"words__enabled": "Enabled",
"words__error": "Error",
"words__fee": "Fee",
+ "words__forget": "Forget",
"words__from": "from",
"words__good_to_know": "Good to know",
"words__important": "Important",
diff --git a/core/translations/order.json b/core/translations/order.json
index 6b38b8c0..eaddfc33 100644
--- a/core/translations/order.json
+++ b/core/translations/order.json
@@ -1122,5 +1122,8 @@
"1120": "words__wipe",
"1121": "lockscreen__unlock",
"1122": "recovery__start_entering",
- "1123": "words__disconnected"
+ "1123": "words__disconnected",
+ "1124": "ble__forget_all",
+ "1125": "words__connect",
+ "1126": "words__forget"
}
diff --git a/core/translations/signatures.json b/core/translations/signatures.json
index 33fdff4a..c2a018f2 100644
--- a/core/translations/signatures.json
+++ b/core/translations/signatures.json
@@ -1,8 +1,8 @@
{
"current": {
- "merkle_root": "f26e3c5785b28115e11f3c6b42cb2dfcd3e3f806932a77d707edc645f4ae0b6f",
- "datetime": "2025-08-26T18:37:58.384080+00:00",
- "commit": "3ec9f04e4aa87e3784cea4e25ff69c986d2b3b62"
+ "merkle_root": "4e747b8a6a61674d9b0f0c535014faa78230f36d2d718c8eb1b0e14460b8df99",
+ "datetime": "2025-08-26T18:38:40.093085+00:00",
+ "commit": "d37c2c14ecac2f3b89acbde6babab6da5c413285"
},
"history": [
{
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.