Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
- ui: Add Bulgarian translation (Thank you, @salif!)
- webext: Try to read app ID from associated desktop entry (Thank you, @michaelbeaumont!)
- ui: Basic recovery from failed attempts to use devices (e.g. flaky bluetooth)
- ui: Display restart reason in case of premature failure

# 0.3.1 [2026-09-05]

Expand Down
59 changes: 50 additions & 9 deletions credentialsd-common/src/model.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,48 @@ pub const BACKGROUND_EVENT_ERROR_INTERNAL: u32 = 0x80000001;
pub const BACKGROUND_EVENT_ERROR_TIMED_OUT: u32 = 0x80000002;
pub const BACKGROUND_EVENT_ERROR_CANCELLED: u32 = 0x80000003;
pub const BACKGROUND_EVENT_ERROR_AUTHENTICATOR: u32 = 0x80000004;
pub const BACKGROUND_EVENT_ERROR_NO_CREDENTIALS: u32 = 0x80000005;
pub const BACKGROUND_EVENT_ERROR_CREDENTIAL_EXCLUDED: u32 = 0x80000006;
pub const BACKGROUND_EVENT_ERROR_PIN_ATTEMPTS_EXHAUSTED: u32 = 0x80000007;
pub const BACKGROUND_EVENT_ERROR_PIN_NOT_SET: u32 = 0x80000008;

/// Machine-readable reason for a transport restart. Shared across all transports.
/// Human-readable strings are built by the UI (gettext).
///
/// The `#[repr(u8)]` layout doubles as the D-Bus wire format via `From<Self> for u8`
/// and `TryFrom<u8> for Self`. Discriminants must remain stable across releases.
/// Discriminant 0 is deliberately unused so that an all-zeroed message is recognisable
/// as invalid by `TryFrom`.
#[repr(u8)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum TransportRestartReason {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We're currently using PascalCase strings to communicate enums everywhere else in the API. I find that easier to read in D-Bus output while debugging, and the extra 10 or so bytes in the message doesn't hurt.

Do you have opinions either way? If not, I'd recommend to make this a string for consistency.

/// The ceremony was interrupted before completing — transport error,
/// user cancelled on their phone/device, or an unrecoverable authenticator
/// error. The user should follow the new prompts to retry.
Interrupted = 1,
/// No matching credentials were found on the device. The user should try
/// a different authenticator.
NoCredentials = 2,
/// Too many incorrect PIN attempts on this device — it is now locked. The
/// user should remove and reinsert the device, or use a different authenticator.
PinAttemptsExhausted = 3,
}

impl From<TransportRestartReason> for u8 {
fn from(value: TransportRestartReason) -> Self {
value as u8
}
}

impl TryFrom<u8> for TransportRestartReason {
type Error = u8;

fn try_from(value: u8) -> Result<Self, Self::Error> {
match value {
1 => Ok(Self::Interrupted),
2 => Ok(Self::NoCredentials),
3 => Ok(Self::PinAttemptsExhausted),
other => Err(other),
}
}
}

/// Credential service events intended to inform the UI.
#[derive(Debug, PartialEq)]
Expand All @@ -38,15 +76,19 @@ pub enum BackgroundEvent {
/// The hybrid ceremony was interrupted by a non-terminating error and a new
/// QR code is about to be issued. The UI should navigate back to the start
/// page so the new QR becomes visible.
HybridRestarting,
HybridRestarting {
reason: TransportRestartReason,
},

NfcIdle,
NfcWaiting,
NfcConnected,
/// The NFC ceremony was interrupted by a non-terminating error and the
/// transport is polling for a new device tap. The UI should navigate back
/// to the start page.
NfcRestarting,
NfcRestarting {
reason: TransportRestartReason,
},

UsbIdle,
UsbWaiting,
Expand All @@ -55,16 +97,15 @@ pub enum BackgroundEvent {
/// The USB ceremony was interrupted by a non-terminating error and the
/// transport is polling for a device. The UI should navigate back to the
/// start page.
UsbRestarting,
UsbRestarting {
reason: TransportRestartReason,
},

ErrorInternal,
ErrorTimedOut,
ErrorCancelled,
ErrorAuthenticator,
ErrorNoCredentials,
ErrorCredentialExcluded,
ErrorPinAttemptsExhausted,
ErrorPinNotSet,
}

/// Emitted when a client enters a PIN for the selected authenticator.
Expand Down
11 changes: 11 additions & 0 deletions credentialsd-ui/data/resources/ui/window.blp
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,17 @@ template $CredentialsUiWindow: ApplicationWindow {
wrap: true;
}
}

Label {
label: bind template.view-model as <$CredentialManagerViewModel>.restart_message;
visible: bind template.view-model as <$CredentialManagerViewModel>.restart_message_visible;
margin-top: 16;
margin-start: 24;
margin-end: 24;
margin-bottom: 8;
wrap: true;
styles ["error"]
}
};
}

Expand Down
Loading