Repository: trezor/trezor-suite
Pull request #17644: feat(suite): normalize sorting of the connected devices in the device…
Author: login: vojtatranta
Created: 2025-03-13T12:39:46Z | Merged: 2025-03-26T10:04:33Z

… list

<!--- Provide a general summary of your changes in the Title above -->

## Description

<!--- Describe your changes in detail -->

## Related Issue

Resolve  https://github.com/trezor/trezor-suite/issues/17533

## Screenshots:



Diff:
diff --git a/packages/suite/src/views/suite/SwitchDevice/SwitchDevice.tsx b/packages/suite/src/views/suite/SwitchDevice/SwitchDevice.tsx
index 59d9a9b5e3c7..23fc27a48cd1 100644
--- a/packages/suite/src/views/suite/SwitchDevice/SwitchDevice.tsx
+++ b/packages/suite/src/views/suite/SwitchDevice/SwitchDevice.tsx
@@ -1,5 +1,5 @@
 import * as deviceUtils from '@suite-common/suite-utils';
-import { selectDevices, selectSelectedDevice } from '@suite-common/wallet-core';
+import { selectDevices } from '@suite-common/wallet-core';
 import { Column } from '@trezor/components';
 import { spacings } from '@trezor/theme';
 
@@ -10,19 +10,13 @@ import { DeviceItem } from './DeviceItem/DeviceItem';
 import { SwitchDeviceModal } from './SwitchDeviceModal';
 
 export const SwitchDevice = ({ onCancel }: ForegroundAppProps) => {
-    const selectedDevice = useSelector(selectSelectedDevice);
     const devices = useSelector(selectDevices);
 
     // exclude selectedDevice from list, because other devices could have a higher priority
     // and we want to have selectedDevice on top
-    const sortedDevices = deviceUtils
-        .getFirstDeviceInstance(devices)
-        .filter(d => !deviceUtils.isSelectedDevice(selectedDevice, d));
-
-    // append selectedDevice at top of the list
-    if (selectedDevice) {
-        sortedDevices.unshift(selectedDevice);
-    }
+    const sortedDevices = deviceUtils.getFirstDeviceInstance(devices, {
+        sortingFn: deviceUtils.sortDevicesForDeviceList,
+    });
 
     return (
         <SwitchDeviceModal isAnimationEnabled onCancel={onCancel}>
diff --git a/suite-common/suite-types/src/device.ts b/suite-common/suite-types/src/device.ts
index 6a69319a9c28..b60cbdd16ccc 100644
--- a/suite-common/suite-types/src/device.ts
+++ b/suite-common/suite-types/src/device.ts
@@ -40,6 +40,7 @@ export interface ExtendedDevice {
 
     instance?: number;
     ts: number;
+    firstConnectedTimestamp: number;
     buttonRequests: ButtonRequest[];
     metadata: DeviceMetadata;
     walletNumber?: number; // number of passphrase wallet intended to be used in UI
diff --git a/suite-common/suite-utils/src/device.ts b/suite-common/suite-utils/src/device.ts
index 4a76ec3343a8..bce348c6ecb4 100644
--- a/suite-common/suite-utils/src/device.ts
+++ b/suite-common/suite-utils/src/device.ts
@@ -344,7 +344,7 @@ export const sortByTimestamp = (devices: TrezorDevice[]): TrezorDevice[] =>
         return b.ts - a.ts;
     });
 
-export const sortByPriority = (a: TrezorDevice, b: TrezorDevice) => {
+const sortByPriority = (a: TrezorDevice, b: TrezorDevice) => {
     // sort by priority:
     // 1. unacquired
     // 2. force remembered
@@ -381,6 +381,18 @@ export const sortByPriority = (a: TrezorDevice, b: TrezorDevice) => {
     return b.ts - a.ts;
 };
 
+export const sortDevicesForDeviceList = (a: TrezorDevice, b: TrezorDevice) => {
+    // sort by priority:
+    // 1. when the device was first connected - oldests come first
+    // when device is "ejected" / completely forgotten, it is last in the list
+
+    if (!b.firstConnectedTimestamp && !a.firstConnectedTimestamp) return 0;
+    if (!b.firstConnectedTimestamp && a.firstConnectedTimestamp) return -1;
+    if (!b.firstConnectedTimestamp || !a.firstConnectedTimestamp) return 1;
+
+    return a.firstConnectedTimestamp - b.firstConnectedTimestamp;
+};
+
 /**
  * Returns all device instances sorted by `instance` field
  * @param {TrezorDevice | undefined} device
@@ -437,7 +449,14 @@ export const getDeviceInstancesGroupedByDeviceId = (devices: TrezorDevice[]): Ac
  * @param {TrezorDevice[]} devices
  * @returns {TrezorDevice[]}
  */
-export const getFirstDeviceInstance = (devices: TrezorDevice[]) =>
+export const getFirstDeviceInstance = (
+    devices: TrezorDevice[],
+    options: {
+        sortingFn: (a: TrezorDevice, b: TrezorDevice) => number;
+    } = {
+        sortingFn: sortByPriority,
+    },
+) =>
     // filter device instances
     devices
         .reduce((result, dev) => {
@@ -451,7 +470,7 @@ export const getFirstDeviceInstance = (devices: TrezorDevice[]) =>
             // base (np passphrase) or first passphrase instance
             return result.concat(instances[0]);
         }, [] as TrezorDevice[])
-        .sort(sortByPriority);
+        .sort(options.sortingFn);
 
 export const getPhysicalDeviceUniqueIds = (devices: TrezorDevice[]) =>
     [...new Set(devices.map(d => d.id))].filter(id => id) as string[];
diff --git a/suite-common/wallet-core/src/device/deviceReducer.ts b/suite-common/wallet-core/src/device/deviceReducer.ts
index 1cef8a7064ef..2c005e528829 100644
--- a/suite-common/wallet-core/src/device/deviceReducer.ts
+++ b/suite-common/wallet-core/src/device/deviceReducer.ts
@@ -149,6 +149,7 @@ const connectDevice = (
             // and ignore this action if so
             return;
         }
+        const currentTime = new Date().getTime();
         draft.devices.push({
             ...device,
             connected: true,
@@ -157,7 +158,11 @@ const connectDevice = (
             buttonRequests: [],
             metadata: {},
             passwords: {},
-            ts: new Date().getTime(),
+            firstConnectedTimestamp:
+                'firstConnectedTimestamp' in device
+                    ? Number(device.firstConnectedTimestamp ?? currentTime)
+                    : currentTime,
+            ts: currentTime,
         });
 
         return;
@@ -189,6 +194,7 @@ const connectDevice = (
 
     const useEmptyPassphrase = getShouldUseEmptyPassphrase(device, deviceInstance, settings);
 
+    const currentTime = new Date().getTime();
     const newDevice: TrezorDevice = {
         ...device,
         state: device._state,
@@ -202,7 +208,11 @@ const connectDevice = (
         buttonRequests: [],
         metadata: {},
         passwords: {},
-        ts: new Date().getTime(),
+        firstConnectedTimestamp:
+            'firstConnectedTimestamp' in device
+                ? Number(device.firstConnectedTimestamp ?? currentTime)
+                : currentTime,
+        ts: currentTime,
     };
 
     // update affected devices
@@ -336,7 +346,10 @@ const updateTimestamp = (draft: DeviceReducerState, device?: TrezorDevice) => {
     const index = deviceUtils.findInstanceIndex(draft.devices, device);
     if (!draft.devices[index]) return;
     // update timestamp
-    draft.devices[index].ts = new Date().getTime();
+    const currentTime = new Date().getTime();
+    draft.devices[index].ts = currentTime;
+    draft.devices[index].firstConnectedTimestamp =
+        draft.devices[index].firstConnectedTimestamp ?? currentTime;
 };
 
 /**
@@ -447,6 +460,7 @@ const createInstance = (draft: DeviceReducerState, device: TrezorDevice) => {
 
     const isPortfolioTrackerDevice = device.id === PORTFOLIO_TRACKER_DEVICE_ID;
 
+    const currentTime = new Date().getTime();
     const newDevice: TrezorDevice = {
         ...device,
         passphraseOnDevice: false,
@@ -456,7 +470,8 @@ const createInstance = (draft: DeviceReducerState, device: TrezorDevice) => {
         state: isPortfolioTrackerDevice ? device.state : undefined,
         walletNumber: undefined,
         authConfirm: false,
-        ts: new Date().getTime(),
+        ts: currentTime,
+        firstConnectedTimestamp: device.firstConnectedTimestamp ?? currentTime,
         buttonRequests: [],
         metadata: {},
         passwords: {},


Reviews:
user: login: jvaclavik
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-03-14T10:43:03Z
user: login: coderabbitai[bot]
body: **Actionable comments posted: 2**

<details>
<summary>📜 Review details</summary>

**Configuration used: CodeRabbit UI**
**Review profile: CHILL**
**Plan: Pro**

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between 834169a2701f4cb9337c6289a59d77f431314ce8 and 61053435eac46394403fb34615f4c47613669c27.

</details>

<details>
<summary>📒 Files selected for processing (5)</summary>

* `packages/suite/src/components/suite/modals/ReduxModal/UserContextModal/TxDetailModal/Detail/AdvancedTxDetails/IODetails/TransactionDetailsModal.tsx` (1 hunks)
* `packages/suite/src/views/suite/SwitchDevice/SwitchDevice.tsx` (2 hunks)
* `suite-common/suite-types/src/device.ts` (1 hunks)
* `suite-common/suite-utils/src/device.ts` (4 hunks)
* `suite-common/wallet-core/src/device/deviceReducer.ts` (7 hunks)

</details>

<details>
<summary>🚧 Files skipped from review as they are similar to previous changes (3)</summary>

* suite-common/suite-types/src/device.ts
* packages/suite/src/views/suite/SwitchDevice/SwitchDevice.tsx
* suite-common/wallet-core/src/device/deviceReducer.ts

</details>

<details>
<summary>🧰 Additional context used</summary>

<details>
<summary>🧬 Code Definitions (1)</summary>

<details>
<summary>suite-common/suite-utils/src/device.ts (1)</summary><blockquote>

<details>
<summary>suite-common/suite-types/src/device.ts (1)</summary>

* `TrezorDevice` (61-61)

</details>

</blockquote></details>

</details><details>
<summary>🪛 Biome (1.9.4)</summary>

<details>
<summary>packages/suite/src/components/suite/modals/ReduxModal/UserContextModal/TxDetailModal/Detail/AdvancedTxDetails/IODetails/TransactionDetailsModal.tsx</summary>

[error] 55-55: Missing key property for this element in iterable.

The order of the items may change, and having a key can help React identify which item was moved.
Check the React documentation. 

(lint/correctness/useJsxKeyInIterable)

---

[error] 57-57: Missing key property for this element in iterable.

The order of the items may change, and having a key can help React identify which item was moved.
Check the React documentation. 

(lint/correctness/useJsxKeyInIterable)

---

[error] 58-58: Missing key property for this element in iterable.

The order of the items may change, and having a key can help React identify which item was moved.
Check the React documentation. 

(lint/correctness/useJsxKeyInIterable)

---

[error] 72-72: Missing key property for this element in iterable.

The order of the items may change, and having a key can help React identify which item was moved.
Check the React documentation. 

(lint/correctness/useJsxKeyInIterable)

---

[error] 74-74: Missing key property for this element in iterable.

The order of the items may change, and having a key can help React identify which item was moved.
Check the React documentation. 

(lint/correctness/useJsxKeyInIterable)

---

[error] 75-75: Missing key property for this element in iterable.

The order of the items may change, and having a key can help React identify which item was moved.
Check the React documentation. 

(lint/correctness/useJsxKeyInIterable)

---

[error] 92-95: Missing key property for this element in iterable.

The order of the items may change, and having a key can help React identify which item was moved.
Check the React documentation. 

(lint/correctness/useJsxKeyInIterable)

---

[error] 98-98: Missing key property for this element in iterable.

The order of the items may change, and having a key can help React identify which item was moved.
Check the React documentation. 

(lint/correctness/useJsxKeyInIterable)

---

[error] 99-99: Missing key property for this element in iterable.

The order of the items may change, and having a key can help React identify which item was moved.
Check the React documentation. 

(lint/correctness/useJsxKeyInIterable)

---

[error] 103-103: Missing key property for this element in iterable.

The order of the items may change, and having a key can help React identify which item was moved.
Check the React documentation. 

(lint/correctness/useJsxKeyInIterable)

---

[error] 104-104: Missing key property for this element in iterable.

The order of the items may change, and having a key can help React identify which item was moved.
Check the React documentation. 

(lint/correctness/useJsxKeyInIterable)

</details>

</details>

</details>

<details>
<summary>⏰ Context from checks skipped due to timeout of 90000ms (6)</summary>

* GitHub Check: build-web
* GitHub Check: build-app
* GitHub Check: Analyze with CodeQL (javascript)
* GitHub Check: prepare_android_test_app
* GitHub Check: Setup and Cache Dependencies
* GitHub Check: EAS Update

</details>

<details>
<summary>🔇 Additional comments (6)</summary><blockquote>

<details>
<summary>suite-common/suite-utils/src/device.ts (3)</summary>

`347-382`: **Function visibility change looks good**

The function has been correctly changed from exported to private as it's now only used internally within this module. This aligns with the encapsulation principle, reducing the public API surface.

---

`384-394`: **New device list sorting implementation looks good**

The new `sortDevicesForDeviceList` function implements the sorting of devices based on their first connection timestamp. This aligns with the PR objective of normalizing device sorting, with oldest connected devices appearing first in the list.

---

`452-473`: <details>
<summary>❓ Verification inconclusive</summary>

**Enhanced flexibility with optional sorting function parameter**

Good improvement to the `getFirstDeviceInstance` function by allowing a custom sorting function to be passed as an option. This maintains backwards compatibility with the default `sortByPriority` while enabling the new timestamp-based sorting when needed.


Run the following script to verify that the new sorting function is being used correctly:

---

🏁 Script executed:

```shell
#!/bin/bash
# Check where getFirstDeviceInstance is being called with the new sortDevicesForDeviceList option
rg 'getFirstDeviceInstance.*sortDevicesForDeviceList' --type ts
```

Length of output: 65

---



</details>

**Attention: Custom Sorting Parameter Usage Verification Needed**

The changes in `suite-common/suite-utils/src/device.ts` (lines 452–473) correctly enhance the flexibility of `getFirstDeviceInstance` by allowing a custom sorting function to be passed. However, the script provided to detect invocations of this functionality using the `sortDevicesForDeviceList` option returned no results. This outcome suggests that either:
 
- The new sorting function parameter isn’t being utilized yet in any calls, or 
- The invocation pattern might be different from what the script is searching for.

**Next Steps:**

- **Review Call Sites:** Please manually verify whether any part of the codebase or integration tests are now passing a custom sorting function (or using the expected `sortDevicesForDeviceList` option) to `getFirstDeviceInstance`.
- **Update Tests/Docs:** If the new parameter is intended for future use, consider adding tests or documentation to clearly demonstrate its usage.
- **Double-check Query Patterns:** Ensure that the naming or call patterns match our search criteria, and update the script if necessary.

</details>
<details>
<summary>packages/suite/src/components/suite/modals/ReduxModal/UserContextModal/TxDetailModal/Detail/AdvancedTxDetails/IODetails/TransactionDetailsModal.tsx (3)</summary>

`1-28`: **Component structure and props look good**

The modal component has a clean structure with appropriate props and styled components. The `CodeBlock` styling is well implemented with theme consideration.

---

`29-52`: **Formatting and display handling is appropriate**

The transaction details display is properly implemented with conditional rendering and formatting of transaction data.

---

`113-116`: **Modal implementation is complete and functional**

The modal structure is well implemented with proper closing functionality and layout.

</details>

</blockquote></details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->
state: COMMENTED
author_association: NONE
submitted_at: 2025-03-25T08:46:19Z
user: login: vojtatranta
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-25T08:57:00Z
user: login: vojtatranta
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-25T08:57:07Z
user: login: coderabbitai[bot]
state: COMMENTED
author_association: NONE
submitted_at: 2025-03-25T08:57:22Z
user: login: coderabbitai[bot]
state: COMMENTED
author_association: NONE
submitted_at: 2025-03-25T08:57:29Z
user: login: coderabbitai[bot]
body: **Actionable comments posted: 0**

<details>
<summary>🧹 Nitpick comments (4)</summary><blockquote>

<details>
<summary>packages/suite/src/components/suite/modals/ReduxModal/UserContextModal/TxDetailModal/Detail/AdvancedTxDetails/IODetails/TransactionDetailsModal.tsx (4)</summary><blockquote>

`59-60`: **Consider showing a placeholder for missing addresses.**

Currently, empty addresses are rendered as empty strings. Consider adding a placeholder text for better user experience.


```diff
-{input.addresses?.[0] ?? ''}
+{input.addresses?.[0] || <Translation id="TR_UNKNOWN_ADDRESS" />}
```

---

`76-77`: **Consider showing a placeholder for missing addresses.**

Similar to inputs, outputs with missing addresses could benefit from a placeholder message.


```diff
-{output.addresses?.[0] ?? ''}
+{output.addresses?.[0] || <Translation id="TR_UNKNOWN_ADDRESS" />}
```

---

`99-100`: **Handle missing token sender consistently.**

For token senders, consider using the same approach as you did with missing addresses elsewhere - showing a placeholder instead of conditionally rendering nothing.


```diff
-{token.from && <Paragraph isMonospaced>{token.from}</Paragraph>}
+<Paragraph isMonospaced>
+  {token.from || <Translation id="TR_UNKNOWN_ADDRESS" />}
+</Paragraph>
```

---

`102-107`: **Remove nested Paragraph components and handle missing recipients consistently.**

There's a nested `Paragraph` component here which is redundant and could cause styling issues. Also, the handling of missing recipient addresses should be consistent with other parts of the component.


```diff
 <Paragraph isMonospaced>
-  {token.to && <Paragraph isMonospaced>{token.to}</Paragraph>}
+  {token.to || <Translation id="TR_UNKNOWN_ADDRESS" />}
   {'\n'}
   {formatAmount(token.amount, token.decimals)}{' '}
   {getDisplaySymbol(token.symbol ?? token.name ?? '')}
 </Paragraph>
```

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 Biome (1.9.4)</summary>

[error] 102-102: Missing key property for this element in iterable.

The order of the items may change, and having a key can help React identify which item was moved.
Check the React documentation. 

(lint/correctness/useJsxKeyInIterable)

</details>

</details>

</blockquote></details>

</blockquote></details>

<details>
<summary>📜 Review details</summary>

**Configuration used: CodeRabbit UI**
**Review profile: CHILL**
**Plan: Pro**

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between 61053435eac46394403fb34615f4c47613669c27 and 49b3876af5e27a494f1c6d132b826f05ab1d2164.

</details>

<details>
<summary>📒 Files selected for processing (5)</summary>

* `packages/suite/src/components/suite/modals/ReduxModal/UserContextModal/TxDetailModal/Detail/AdvancedTxDetails/IODetails/TransactionDetailsModal.tsx` (1 hunks)
* `packages/suite/src/views/suite/SwitchDevice/SwitchDevice.tsx` (2 hunks)
* `suite-common/suite-types/src/device.ts` (1 hunks)
* `suite-common/suite-utils/src/device.ts` (4 hunks)
* `suite-common/wallet-core/src/device/deviceReducer.ts` (7 hunks)

</details>

<details>
<summary>🚧 Files skipped from review as they are similar to previous changes (3)</summary>

* suite-common/suite-types/src/device.ts
* suite-common/wallet-core/src/device/deviceReducer.ts
* packages/suite/src/views/suite/SwitchDevice/SwitchDevice.tsx

</details>

<details>
<summary>🧰 Additional context used</summary>

<details>
<summary>🧠 Learnings (1)</summary>

<details>
<summary>packages/suite/src/components/suite/modals/ReduxModal/UserContextModal/TxDetailModal/Detail/AdvancedTxDetails/IODetails/TransactionDetailsModal.tsx (2)</summary>

```
Learnt from: vojtatranta
PR: trezor/trezor-suite#17644
File: packages/suite/src/components/suite/modals/ReduxModal/UserContextModal/TxDetailModal/Detail/AdvancedTxDetails/IODetails/TransactionDetailsModal.tsx:53-86
Timestamp: 2025-03-25T08:57:28.304Z
Learning: The developer (vojtatranta) has decided not to add React key props to iterable elements in the TransactionDetailsModal component's input and output mappings.
```

```
Learnt from: vojtatranta
PR: trezor/trezor-suite#17644
File: packages/suite/src/components/suite/modals/ReduxModal/UserContextModal/TxDetailModal/Detail/AdvancedTxDetails/IODetails/TransactionDetailsModal.tsx:87-112
Timestamp: 2025-03-25T08:57:21.694Z
Learning: Missing React keys in iterables in the `TransactionDetailsModal.tsx` file are accepted by the project maintainers, specifically in the token transfers mapping.
```

</details>

</details><details>
<summary>🧬 Code Definitions (1)</summary>

<details>
<summary>suite-common/suite-utils/src/device.ts (1)</summary><blockquote>

<details>
<summary>suite-common/suite-types/src/device.ts (1)</summary>

* `TrezorDevice` (61-61)

</details>

</blockquote></details>

</details><details>
<summary>🪛 Biome (1.9.4)</summary>

<details>
<summary>packages/suite/src/components/suite/modals/ReduxModal/UserContextModal/TxDetailModal/Detail/AdvancedTxDetails/IODetails/TransactionDetailsModal.tsx</summary>

[error] 55-55: Missing key property for this element in iterable.

The order of the items may change, and having a key can help React identify which item was moved.
Check the React documentation. 

(lint/correctness/useJsxKeyInIterable)

---

[error] 57-57: Missing key property for this element in iterable.

The order of the items may change, and having a key can help React identify which item was moved.
Check the React documentation. 

(lint/correctness/useJsxKeyInIterable)

---

[error] 58-58: Missing key property for this element in iterable.

The order of the items may change, and having a key can help React identify which item was moved.
Check the React documentation. 

(lint/correctness/useJsxKeyInIterable)

---

[error] 72-72: Missing key property for this element in iterable.

The order of the items may change, and having a key can help React identify which item was moved.
Check the React documentation. 

(lint/correctness/useJsxKeyInIterable)

---

[error] 74-74: Missing key property for this element in iterable.

The order of the items may change, and having a key can help React identify which item was moved.
Check the React documentation. 

(lint/correctness/useJsxKeyInIterable)

---

[error] 75-75: Missing key property for this element in iterable.

The order of the items may change, and having a key can help React identify which item was moved.
Check the React documentation. 

(lint/correctness/useJsxKeyInIterable)

---

[error] 92-95: Missing key property for this element in iterable.

The order of the items may change, and having a key can help React identify which item was moved.
Check the React documentation. 

(lint/correctness/useJsxKeyInIterable)

---

[error] 98-98: Missing key property for this element in iterable.

The order of the items may change, and having a key can help React identify which item was moved.
Check the React documentation. 

(lint/correctness/useJsxKeyInIterable)

---

[error] 101-101: Missing key property for this element in iterable.

The order of the items may change, and having a key can help React identify which item was moved.
Check the React documentation. 

(lint/correctness/useJsxKeyInIterable)

---

[error] 102-102: Missing key property for this element in iterable.

The order of the items may change, and having a key can help React identify which item was moved.
Check the React documentation. 

(lint/correctness/useJsxKeyInIterable)

</details>

</details>

</details>

<details>
<summary>⏰ Context from checks skipped due to timeout of 90000ms (6)</summary>

* GitHub Check: build-app
* GitHub Check: build-web
* GitHub Check: prepare_android_test_app
* GitHub Check: EAS Update
* GitHub Check: Setup and Cache Dependencies
* GitHub Check: Analyze with CodeQL (javascript)

</details>

<details>
<summary>🔇 Additional comments (7)</summary><blockquote>

<details>
<summary>packages/suite/src/components/suite/modals/ReduxModal/UserContextModal/TxDetailModal/Detail/AdvancedTxDetails/IODetails/TransactionDetailsModal.tsx (3)</summary>

`1-13`: **Well-structured imports and dependencies.**

The component imports all necessary dependencies for a transaction details modal, including UI components, utility functions, and translation support. The organization of imports is clean and logical.

---

`14-22`: **Good theme-aware styling for the code block.**

The `CodeBlock` styled component properly adapts to the current theme and includes appropriate styling for code display with proper word breaking and white space handling.

---

`53-69`: **Missing key props in iterable elements are accepted.**

I notice the component maps over collections without providing key props to the React elements. Based on the retrieved learnings, this is an intentional decision that has been accepted by the project maintainers.



Also applies to: 70-86, 90-109

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 Biome (1.9.4)</summary>

[error] 55-55: Missing key property for this element in iterable.

The order of the items may change, and having a key can help React identify which item was moved.
Check the React documentation. 

(lint/correctness/useJsxKeyInIterable)

---

[error] 57-57: Missing key property for this element in iterable.

The order of the items may change, and having a key can help React identify which item was moved.
Check the React documentation. 

(lint/correctness/useJsxKeyInIterable)

---

[error] 58-58: Missing key property for this element in iterable.

The order of the items may change, and having a key can help React identify which item was moved.
Check the React documentation. 

(lint/correctness/useJsxKeyInIterable)

</details>

</details>

</details>
<details>
<summary>suite-common/suite-utils/src/device.ts (4)</summary>

`347-347`: **Modified function visibility: `sortByPriority` is now private**

The `sortByPriority` function has been changed from exported to private by removing the `export` keyword. This encapsulates the sorting logic implementation details and keeps the public API cleaner.

---

`389-393`: **Edge case handling in timestamp-based sorting**

The timestamp comparison logic correctly handles edge cases where timestamps might be undefined, which is essential for robust sorting behavior. The function follows a similar pattern to the existing `sortByTimestamp` function at lines 336-345.

---

`452-459`: <details>
<summary>✅ Verification successful</summary>

**Enhanced flexibility of `getFirstDeviceInstance` with customizable sorting**

Good enhancement to make the sorting behavior configurable by:
1. Adding an options parameter with a sorting function
2. Setting a sensible default to maintain backward compatibility
3. Consistently applying the sorting function from options

This change makes the function more flexible while preserving existing behavior by default, which is a good practice when evolving APIs.


Let's verify this change doesn't break existing usage:





Also applies to: 473-473

---

🏁 Script executed:

```shell
#!/bin/bash
# Find all places where getFirstDeviceInstance is called to ensure they're updated or compatible
rg "getFirstDeviceInstance\(" --type ts
```

Length of output: 414

---



</details>

**Enhanced flexibility of `getFirstDeviceInstance` verified as backward-compatible and correctly integrated.**  
- The default sorting behavior is maintained when no sorting function is provided, as seen in `suite-common/wallet-core/src/device/deviceThunks.ts`.  
- Custom sorting is also used appropriately in `packages/suite/src/views/suite/SwitchDevice/SwitchDevice.tsx` and verified in tests at `packages/suite/src/utils/suite/__tests__/device.test.ts`.  

Good work on introducing configurability while preserving existing functionality.

---

`384-394`: <details>
<summary>❌ Incorrect review comment</summary>

**New sorting function implements timestamp-based ordering for device list**

The new `sortDevicesForDeviceList` function correctly implements the requested sorting behavior for connected devices based on their first connection timestamp, with older devices appearing first in the list.


However, there's a small discrepancy between the comment and the implementation:
- Line 387-388 comment states "when device is ejected, it is last in the list"
- But lines 390-391 prioritize devices without timestamps (which would include ejected devices) to appear before devices with timestamps

---

🏁 Script executed:

```shell
#!/bin/bash
# Check if ejected devices get their timestamp removed in the reducer
rg -A 10 "ejected|forgotten|disconnect" --type ts | grep -i "firstConnectedTimestamp"
```

Length of output: 535

---



</details>

**Device Sorting Logic Verified – No Discrepancy Found**

After verifying the reducer logic with the grep output and analyzing the comparator function, the implementation in
```
suite-common/suite-utils/src/device.ts
```
correctly orders devices. In summary:
- When one device has a valid timestamp while the other does not, the device with the timestamp is correctly placed before the one without.
- Devices missing a timestamp (which represent ejected/forgotten devices) end up at the end of the sorted list.
- The condition ordering in the comparator function ensures that if both devices are missing a timestamp, they are treated as equal.

The review comment’s concern that lines 390–391 prioritize devices without timestamps before devices with timestamps is incorrect. The code’s behavior aligns with the intended design as described in the comment.



> Likely an incorrect or invalid review comment.

</details>

</blockquote></details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->
state: COMMENTED
author_association: NONE
submitted_at: 2025-03-25T09:03:00Z

Review comments:
diff_hunk: @@ -0,0 +1,116 @@
+import React from 'react';
+
+import styled, { useTheme } from 'styled-components';
+
+import { getDisplaySymbol } from '@suite-common/wallet-config';
+import { WalletAccountTransaction } from '@suite-common/wallet-types';
+import { formatAmount, formatNetworkAmount } from '@suite-common/wallet-utils';
+import { Column, NewModal, Paragraph } from '@trezor/components';
+import { borders, spacings, spacingsPx } from '@trezor/theme';
+
+import { Translation } from 'src/components/suite';
+import { useSelector } from 'src/hooks/suite/useSelector';
+
+const CodeBlock = styled.code<{ $isDarkTheme: boolean }>`
+    background-color: ${({ theme, $isDarkTheme }) =>
+        theme[$isDarkTheme ? 'textDefaultInverted' : 'backgroundNeutralDisabled']};
+    padding: ${spacingsPx.sm};
+    border-radius: ${borders.radii.sm};
+    word-break: break-all;
+    overflow-wrap: anywhere;
+    white-space: pre-wrap;
+`;
+
+type TransactionDetailsModalProps = {
+    tx: WalletAccountTransaction;
+    onClose: () => void;
+};
+
+export const TransactionDetailsModal = ({ tx, onClose }: TransactionDetailsModalProps) => {
+    const network = useSelector(state => state.wallet.selectedAccount.network);
+    const theme = useTheme();
+    const isDarkTheme = theme.variant === 'dark';
+
+    const formattedSymbol = tx.symbol && getDisplaySymbol(tx.symbol);
+
+    return (
+        <NewModal
+            isBackdropCancelable
+            heading={<Translation id="TR_TRANSACTION_DETAILS" />}
+            data-e2e="tx_details_modal"
+            onCancel={onClose}
+        >
+            <Column gap={spacings.lg}>
+                <CodeBlock $isDarkTheme={isDarkTheme}>
+                    <Translation id="TR_TXID" />:{'\n'}
+                    <Paragraph isMonospaced>{tx.txid}</Paragraph>
+                    {tx.amount && (
+                        <>
+                            {formatNetworkAmount(tx.amount, tx.symbol)} {formattedSymbol}
+                        </>
+                    )}
+                    {'\n'}
+                    {tx.details.vin.map((input, index) => (
+                        <>
+                            <Translation id="TR_INPUTS" /> {index + 1}
+                            {'\n'}
+                            <Translation id="TR_FROM" />:{'\n'}
+                            <Paragraph isMonospaced>
+                                {input.addresses?.[0] || <Translation id="TR_UNKNOWN_ADDRESS" />}
+                                {'\n'}
+                                {input.value && (
+                                    <>
+                                        {formatNetworkAmount(input.value, tx.symbol)}{' '}
+                                        {formattedSymbol}
+                                    </>
+                                )}
+                            </Paragraph>
+                        </>
+                    ))}
+                    {tx.details.vout.map((output, index) => (
+                        <>
+                            <Translation id="TR_OUTPUTS" /> {index + 1}
+                            {'\n'}
+                            <Translation id="TR_TO" />:{'\n'}
+                            <Paragraph isMonospaced>
+                                {output.addresses?.[0] || <Translation id="TR_UNKNOWN_ADDRESS" />}
+                                {'\n'}
+                                {output.isAddress && output.value && (
+                                    <>
+                                        {formatNetworkAmount(output.value, tx.symbol)}{' '}
+                                        {formattedSymbol}
+                                    </>
+                                )}
+                            </Paragraph>
+                        </>
+                    ))}
+                    {/* Display Solana token transfers if present */}
+                    {network?.networkType === 'solana' &&
+                        tx.tokens.length > 0 &&
+                        tx.tokens.map((token, index) => (
+                            <>
+                                <Translation
+                                    id="TR_TOKEN_TRANSFERS"
+                                    values={{ standard: token.standard }}
+                                />{' '}
+                                {index + 1}
+                                {'\n'}
+                                <Translation id="TR_FROM" />:{'\n'}
+                                <Paragraph isMonospaced>
+                                    {token.from || <Translation id="TR_UNKNOWN_ADDRESS" />}
+                                </Paragraph>
+                                {'\n'}
+                                <Translation id="TR_TO" />:{'\n'}
+                                <Paragraph isMonospaced>
+                                    {token.to || <Translation id="TR_UNKNOWN_ADDRESS" />}
+                                    {'\n'}
+                                    {formatAmount(token.amount, token.decimals)}{' '}
+                                    {getDisplaySymbol(token.symbol ?? token.name ?? '')}
+                                </Paragraph>
+                            </>
+                        ))}
+                </CodeBlock>
path: packages/suite/src/components/suite/modals/ReduxModal/UserContextModal/TxDetailModal/Detail/AdvancedTxDetails/IODetails/TransactionDetailsModal.tsx
user: login: coderabbitai[bot]
body: _⚠️ Potential issue_

**Missing keys in token transfers mapping**

Similar to the previous issue, the token transfers mapping also needs unique keys for the React elements.

Add unique keys to the elements in the token transfers mapping:

```diff
 {network?.networkType === 'solana' &&
   tx.tokens.length > 0 &&
-  tx.tokens.map((token, index) => (
+  tx.tokens.map((token, index) => (
+    <React.Fragment key={`token-${index}`}>
       <Translation
         id="TR_TOKEN_TRANSFERS"
         values={{ standard: token.standard }}
       />{' '}
       {index + 1}
       {'\n'}
       <Translation id="TR_FROM" />:{'\n'}
       <Paragraph isMonospaced>
         {token.from || <Translation id="TR_UNKNOWN_ADDRESS" />}
       </Paragraph>
       {'\n'}
       <Translation id="TR_TO" />:{'\n'}
       <Paragraph isMonospaced>
         {token.to || <Translation id="TR_UNKNOWN_ADDRESS" />}
         {'\n'}
         {formatAmount(token.amount, token.decimals)}{' '}
         {getDisplaySymbol(token.symbol ?? token.name ?? '')}
       </Paragraph>
+    </React.Fragment>
   ))}
```


<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

`````suggestion
                    {/* Display Solana token transfers if present */}
                    {network?.networkType === 'solana' &&
                        tx.tokens.length > 0 &&
                        tx.tokens.map((token, index) => (
                            <React.Fragment key={`token-${index}`}>
                                <Translation
                                    id="TR_TOKEN_TRANSFERS"
                                    values={{ standard: token.standard }}
                                />{' '}
                                {index + 1}
                                {'\n'}
                                <Translation id="TR_FROM" />:{'\n'}
                                <Paragraph isMonospaced>
                                    {token.from || <Translation id="TR_UNKNOWN_ADDRESS" />}
                                </Paragraph>
                                {'\n'}
                                <Translation id="TR_TO" />:{'\n'}
                                <Paragraph isMonospaced>
                                    {token.to || <Translation id="TR_UNKNOWN_ADDRESS" />}
                                    {'\n'}
                                    {formatAmount(token.amount, token.decimals)}{' '}
                                    {getDisplaySymbol(token.symbol ?? token.name ?? '')}
                                </Paragraph>
                            </React.Fragment>
                        ))}
                </CodeBlock>
`````

</details>

<!-- suggestion_end -->

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 Biome (1.9.4)</summary>

[error] 92-95: Missing key property for this element in iterable.

The order of the items may change, and having a key can help React identify which item was moved.
Check the React documentation. 

(lint/correctness/useJsxKeyInIterable)

---

[error] 98-98: Missing key property for this element in iterable.

The order of the items may change, and having a key can help React identify which item was moved.
Check the React documentation. 

(lint/correctness/useJsxKeyInIterable)

---

[error] 99-99: Missing key property for this element in iterable.

The order of the items may change, and having a key can help React identify which item was moved.
Check the React documentation. 

(lint/correctness/useJsxKeyInIterable)

---

[error] 103-103: Missing key property for this element in iterable.

The order of the items may change, and having a key can help React identify which item was moved.
Check the React documentation. 

(lint/correctness/useJsxKeyInIterable)

---

[error] 104-104: Missing key property for this element in iterable.

The order of the items may change, and having a key can help React identify which item was moved.
Check the React documentation. 

(lint/correctness/useJsxKeyInIterable)

</details>

</details>

<!-- This is an auto-generated reply by CodeRabbit -->
created_at: 2025-03-25T08:46:18Z
author_association: NONE
diff_hunk: @@ -0,0 +1,116 @@
+import React from 'react';
+
+import styled, { useTheme } from 'styled-components';
+
+import { getDisplaySymbol } from '@suite-common/wallet-config';
+import { WalletAccountTransaction } from '@suite-common/wallet-types';
+import { formatAmount, formatNetworkAmount } from '@suite-common/wallet-utils';
+import { Column, NewModal, Paragraph } from '@trezor/components';
+import { borders, spacings, spacingsPx } from '@trezor/theme';
+
+import { Translation } from 'src/components/suite';
+import { useSelector } from 'src/hooks/suite/useSelector';
+
+const CodeBlock = styled.code<{ $isDarkTheme: boolean }>`
+    background-color: ${({ theme, $isDarkTheme }) =>
+        theme[$isDarkTheme ? 'textDefaultInverted' : 'backgroundNeutralDisabled']};
+    padding: ${spacingsPx.sm};
+    border-radius: ${borders.radii.sm};
+    word-break: break-all;
+    overflow-wrap: anywhere;
+    white-space: pre-wrap;
+`;
+
+type TransactionDetailsModalProps = {
+    tx: WalletAccountTransaction;
+    onClose: () => void;
+};
+
+export const TransactionDetailsModal = ({ tx, onClose }: TransactionDetailsModalProps) => {
+    const network = useSelector(state => state.wallet.selectedAccount.network);
+    const theme = useTheme();
+    const isDarkTheme = theme.variant === 'dark';
+
+    const formattedSymbol = tx.symbol && getDisplaySymbol(tx.symbol);
+
+    return (
+        <NewModal
+            isBackdropCancelable
+            heading={<Translation id="TR_TRANSACTION_DETAILS" />}
+            data-e2e="tx_details_modal"
+            onCancel={onClose}
+        >
+            <Column gap={spacings.lg}>
+                <CodeBlock $isDarkTheme={isDarkTheme}>
+                    <Translation id="TR_TXID" />:{'\n'}
+                    <Paragraph isMonospaced>{tx.txid}</Paragraph>
+                    {tx.amount && (
+                        <>
+                            {formatNetworkAmount(tx.amount, tx.symbol)} {formattedSymbol}
+                        </>
+                    )}
+                    {'\n'}
+                    {tx.details.vin.map((input, index) => (
+                        <>
+                            <Translation id="TR_INPUTS" /> {index + 1}
+                            {'\n'}
+                            <Translation id="TR_FROM" />:{'\n'}
+                            <Paragraph isMonospaced>
+                                {input.addresses?.[0] || <Translation id="TR_UNKNOWN_ADDRESS" />}
+                                {'\n'}
+                                {input.value && (
+                                    <>
+                                        {formatNetworkAmount(input.value, tx.symbol)}{' '}
+                                        {formattedSymbol}
+                                    </>
+                                )}
+                            </Paragraph>
+                        </>
+                    ))}
+                    {tx.details.vout.map((output, index) => (
+                        <>
+                            <Translation id="TR_OUTPUTS" /> {index + 1}
+                            {'\n'}
+                            <Translation id="TR_TO" />:{'\n'}
+                            <Paragraph isMonospaced>
+                                {output.addresses?.[0] || <Translation id="TR_UNKNOWN_ADDRESS" />}
+                                {'\n'}
+                                {output.isAddress && output.value && (
+                                    <>
+                                        {formatNetworkAmount(output.value, tx.symbol)}{' '}
+                                        {formattedSymbol}
+                                    </>
+                                )}
+                            </Paragraph>
+                        </>
+                    ))}
path: packages/suite/src/components/suite/modals/ReduxModal/UserContextModal/TxDetailModal/Detail/AdvancedTxDetails/IODetails/TransactionDetailsModal.tsx
user: login: coderabbitai[bot]
body: _⚠️ Potential issue_

**Missing keys in iterable elements**

React requires a unique `key` prop for elements in iterables to help with rendering optimization and maintaining component state.

Add unique keys to the elements in the input and output mapping:

```diff
-{tx.details.vin.map((input, index) => (
+{tx.details.vin.map((input, index) => (
+  <React.Fragment key={`input-${index}`}>
     <Translation id="TR_INPUTS" /> {index + 1}
     {'\n'}
     <Translation id="TR_FROM" />:{'\n'}
     <Paragraph isMonospaced>
       {input.addresses?.[0] || <Translation id="TR_UNKNOWN_ADDRESS" />}
       {'\n'}
       {input.value && (
         <>
           {formatNetworkAmount(input.value, tx.symbol)}{' '}
           {formattedSymbol}
         </>
       )}
     </Paragraph>
+  </React.Fragment>
 ))}

-{tx.details.vout.map((output, index) => (
+{tx.details.vout.map((output, index) => (
+  <React.Fragment key={`output-${index}`}>
     <Translation id="TR_OUTPUTS" /> {index + 1}
     {'\n'}
     <Translation id="TR_TO" />:{'\n'}
     <Paragraph isMonospaced>
       {output.addresses?.[0] || <Translation id="TR_UNKNOWN_ADDRESS" />}
       {'\n'}
       {output.isAddress && output.value && (
         <>
           {formatNetworkAmount(output.value, tx.symbol)}{' '}
           {formattedSymbol}
         </>
       )}
     </Paragraph>
+  </React.Fragment>
 ))}
```


<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

`````suggestion
                    {tx.details.vin.map((input, index) => (
                        <React.Fragment key={`input-${index}`}>
                            <Translation id="TR_INPUTS" /> {index + 1}
                            {'\n'}
                            <Translation id="TR_FROM" />:{'\n'}
                            <Paragraph isMonospaced>
                                {input.addresses?.[0] || <Translation id="TR_UNKNOWN_ADDRESS" />}
                                {'\n'}
                                {input.value && (
                                    <>
                                        {formatNetworkAmount(input.value, tx.symbol)}{' '}
                                        {formattedSymbol}
                                    </>
                                )}
                            </Paragraph>
                        </React.Fragment>
                    ))}
                    {tx.details.vout.map((output, index) => (
                        <React.Fragment key={`output-${index}`}>
                            <Translation id="TR_OUTPUTS" /> {index + 1}
                            {'\n'}
                            <Translation id="TR_TO" />:{'\n'}
                            <Paragraph isMonospaced>
                                {output.addresses?.[0] || <Translation id="TR_UNKNOWN_ADDRESS" />}
                                {'\n'}
                                {output.isAddress && output.value && (
                                    <>
                                        {formatNetworkAmount(output.value, tx.symbol)}{' '}
                                        {formattedSymbol}
                                    </>
                                )}
                            </Paragraph>
                        </React.Fragment>
                    ))}
`````

</details>

<!-- suggestion_end -->

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 Biome (1.9.4)</summary>

[error] 55-55: Missing key property for this element in iterable.

The order of the items may change, and having a key can help React identify which item was moved.
Check the React documentation. 

(lint/correctness/useJsxKeyInIterable)

---

[error] 57-57: Missing key property for this element in iterable.

The order of the items may change, and having a key can help React identify which item was moved.
Check the React documentation. 

(lint/correctness/useJsxKeyInIterable)

---

[error] 58-58: Missing key property for this element in iterable.

The order of the items may change, and having a key can help React identify which item was moved.
Check the React documentation. 

(lint/correctness/useJsxKeyInIterable)

---

[error] 72-72: Missing key property for this element in iterable.

The order of the items may change, and having a key can help React identify which item was moved.
Check the React documentation. 

(lint/correctness/useJsxKeyInIterable)

---

[error] 74-74: Missing key property for this element in iterable.

The order of the items may change, and having a key can help React identify which item was moved.
Check the React documentation. 

(lint/correctness/useJsxKeyInIterable)

---

[error] 75-75: Missing key property for this element in iterable.

The order of the items may change, and having a key can help React identify which item was moved.
Check the React documentation. 

(lint/correctness/useJsxKeyInIterable)

</details>

</details>

<!-- This is an auto-generated reply by CodeRabbit -->
created_at: 2025-03-25T08:46:18Z
author_association: NONE
diff_hunk: @@ -0,0 +1,116 @@
+import React from 'react';
+
+import styled, { useTheme } from 'styled-components';
+
+import { getDisplaySymbol } from '@suite-common/wallet-config';
+import { WalletAccountTransaction } from '@suite-common/wallet-types';
+import { formatAmount, formatNetworkAmount } from '@suite-common/wallet-utils';
+import { Column, NewModal, Paragraph } from '@trezor/components';
+import { borders, spacings, spacingsPx } from '@trezor/theme';
+
+import { Translation } from 'src/components/suite';
+import { useSelector } from 'src/hooks/suite/useSelector';
+
+const CodeBlock = styled.code<{ $isDarkTheme: boolean }>`
+    background-color: ${({ theme, $isDarkTheme }) =>
+        theme[$isDarkTheme ? 'textDefaultInverted' : 'backgroundNeutralDisabled']};
+    padding: ${spacingsPx.sm};
+    border-radius: ${borders.radii.sm};
+    word-break: break-all;
+    overflow-wrap: anywhere;
+    white-space: pre-wrap;
+`;
+
+type TransactionDetailsModalProps = {
+    tx: WalletAccountTransaction;
+    onClose: () => void;
+};
+
+export const TransactionDetailsModal = ({ tx, onClose }: TransactionDetailsModalProps) => {
+    const network = useSelector(state => state.wallet.selectedAccount.network);
+    const theme = useTheme();
+    const isDarkTheme = theme.variant === 'dark';
+
+    const formattedSymbol = tx.symbol && getDisplaySymbol(tx.symbol);
+
+    return (
+        <NewModal
+            isBackdropCancelable
+            heading={<Translation id="TR_TRANSACTION_DETAILS" />}
+            data-e2e="tx_details_modal"
+            onCancel={onClose}
+        >
+            <Column gap={spacings.lg}>
+                <CodeBlock $isDarkTheme={isDarkTheme}>
+                    <Translation id="TR_TXID" />:{'\n'}
+                    <Paragraph isMonospaced>{tx.txid}</Paragraph>
+                    {tx.amount && (
+                        <>
+                            {formatNetworkAmount(tx.amount, tx.symbol)} {formattedSymbol}
+                        </>
+                    )}
+                    {'\n'}
+                    {tx.details.vin.map((input, index) => (
+                        <>
+                            <Translation id="TR_INPUTS" /> {index + 1}
+                            {'\n'}
+                            <Translation id="TR_FROM" />:{'\n'}
+                            <Paragraph isMonospaced>
+                                {input.addresses?.[0] || <Translation id="TR_UNKNOWN_ADDRESS" />}
+                                {'\n'}
+                                {input.value && (
+                                    <>
+                                        {formatNetworkAmount(input.value, tx.symbol)}{' '}
+                                        {formattedSymbol}
+                                    </>
+                                )}
+                            </Paragraph>
+                        </>
+                    ))}
+                    {tx.details.vout.map((output, index) => (
+                        <>
+                            <Translation id="TR_OUTPUTS" /> {index + 1}
+                            {'\n'}
+                            <Translation id="TR_TO" />:{'\n'}
+                            <Paragraph isMonospaced>
+                                {output.addresses?.[0] || <Translation id="TR_UNKNOWN_ADDRESS" />}
+                                {'\n'}
+                                {output.isAddress && output.value && (
+                                    <>
+                                        {formatNetworkAmount(output.value, tx.symbol)}{' '}
+                                        {formattedSymbol}
+                                    </>
+                                )}
+                            </Paragraph>
+                        </>
+                    ))}
+                    {/* Display Solana token transfers if present */}
+                    {network?.networkType === 'solana' &&
+                        tx.tokens.length > 0 &&
+                        tx.tokens.map((token, index) => (
+                            <>
+                                <Translation
+                                    id="TR_TOKEN_TRANSFERS"
+                                    values={{ standard: token.standard }}
+                                />{' '}
+                                {index + 1}
+                                {'\n'}
+                                <Translation id="TR_FROM" />:{'\n'}
+                                <Paragraph isMonospaced>
+                                    {token.from || <Translation id="TR_UNKNOWN_ADDRESS" />}
+                                </Paragraph>
+                                {'\n'}
+                                <Translation id="TR_TO" />:{'\n'}
+                                <Paragraph isMonospaced>
+                                    {token.to || <Translation id="TR_UNKNOWN_ADDRESS" />}
+                                    {'\n'}
+                                    {formatAmount(token.amount, token.decimals)}{' '}
+                                    {getDisplaySymbol(token.symbol ?? token.name ?? '')}
+                                </Paragraph>
+                            </>
+                        ))}
+                </CodeBlock>
path: packages/suite/src/components/suite/modals/ReduxModal/UserContextModal/TxDetailModal/Detail/AdvancedTxDetails/IODetails/TransactionDetailsModal.tsx
user: login: vojtatranta
body: this won't change
created_at: 2025-03-25T08:57:00Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,116 @@
+import React from 'react';
+
+import styled, { useTheme } from 'styled-components';
+
+import { getDisplaySymbol } from '@suite-common/wallet-config';
+import { WalletAccountTransaction } from '@suite-common/wallet-types';
+import { formatAmount, formatNetworkAmount } from '@suite-common/wallet-utils';
+import { Column, NewModal, Paragraph } from '@trezor/components';
+import { borders, spacings, spacingsPx } from '@trezor/theme';
+
+import { Translation } from 'src/components/suite';
+import { useSelector } from 'src/hooks/suite/useSelector';
+
+const CodeBlock = styled.code<{ $isDarkTheme: boolean }>`
+    background-color: ${({ theme, $isDarkTheme }) =>
+        theme[$isDarkTheme ? 'textDefaultInverted' : 'backgroundNeutralDisabled']};
+    padding: ${spacingsPx.sm};
+    border-radius: ${borders.radii.sm};
+    word-break: break-all;
+    overflow-wrap: anywhere;
+    white-space: pre-wrap;
+`;
+
+type TransactionDetailsModalProps = {
+    tx: WalletAccountTransaction;
+    onClose: () => void;
+};
+
+export const TransactionDetailsModal = ({ tx, onClose }: TransactionDetailsModalProps) => {
+    const network = useSelector(state => state.wallet.selectedAccount.network);
+    const theme = useTheme();
+    const isDarkTheme = theme.variant === 'dark';
+
+    const formattedSymbol = tx.symbol && getDisplaySymbol(tx.symbol);
+
+    return (
+        <NewModal
+            isBackdropCancelable
+            heading={<Translation id="TR_TRANSACTION_DETAILS" />}
+            data-e2e="tx_details_modal"
+            onCancel={onClose}
+        >
+            <Column gap={spacings.lg}>
+                <CodeBlock $isDarkTheme={isDarkTheme}>
+                    <Translation id="TR_TXID" />:{'\n'}
+                    <Paragraph isMonospaced>{tx.txid}</Paragraph>
+                    {tx.amount && (
+                        <>
+                            {formatNetworkAmount(tx.amount, tx.symbol)} {formattedSymbol}
+                        </>
+                    )}
+                    {'\n'}
+                    {tx.details.vin.map((input, index) => (
+                        <>
+                            <Translation id="TR_INPUTS" /> {index + 1}
+                            {'\n'}
+                            <Translation id="TR_FROM" />:{'\n'}
+                            <Paragraph isMonospaced>
+                                {input.addresses?.[0] || <Translation id="TR_UNKNOWN_ADDRESS" />}
+                                {'\n'}
+                                {input.value && (
+                                    <>
+                                        {formatNetworkAmount(input.value, tx.symbol)}{' '}
+                                        {formattedSymbol}
+                                    </>
+                                )}
+                            </Paragraph>
+                        </>
+                    ))}
+                    {tx.details.vout.map((output, index) => (
+                        <>
+                            <Translation id="TR_OUTPUTS" /> {index + 1}
+                            {'\n'}
+                            <Translation id="TR_TO" />:{'\n'}
+                            <Paragraph isMonospaced>
+                                {output.addresses?.[0] || <Translation id="TR_UNKNOWN_ADDRESS" />}
+                                {'\n'}
+                                {output.isAddress && output.value && (
+                                    <>
+                                        {formatNetworkAmount(output.value, tx.symbol)}{' '}
+                                        {formattedSymbol}
+                                    </>
+                                )}
+                            </Paragraph>
+                        </>
+                    ))}
path: packages/suite/src/components/suite/modals/ReduxModal/UserContextModal/TxDetailModal/Detail/AdvancedTxDetails/IODetails/TransactionDetailsModal.tsx
user: login: vojtatranta
body: wont change
created_at: 2025-03-25T08:57:07Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,116 @@
+import React from 'react';
+
+import styled, { useTheme } from 'styled-components';
+
+import { getDisplaySymbol } from '@suite-common/wallet-config';
+import { WalletAccountTransaction } from '@suite-common/wallet-types';
+import { formatAmount, formatNetworkAmount } from '@suite-common/wallet-utils';
+import { Column, NewModal, Paragraph } from '@trezor/components';
+import { borders, spacings, spacingsPx } from '@trezor/theme';
+
+import { Translation } from 'src/components/suite';
+import { useSelector } from 'src/hooks/suite/useSelector';
+
+const CodeBlock = styled.code<{ $isDarkTheme: boolean }>`
+    background-color: ${({ theme, $isDarkTheme }) =>
+        theme[$isDarkTheme ? 'textDefaultInverted' : 'backgroundNeutralDisabled']};
+    padding: ${spacingsPx.sm};
+    border-radius: ${borders.radii.sm};
+    word-break: break-all;
+    overflow-wrap: anywhere;
+    white-space: pre-wrap;
+`;
+
+type TransactionDetailsModalProps = {
+    tx: WalletAccountTransaction;
+    onClose: () => void;
+};
+
+export const TransactionDetailsModal = ({ tx, onClose }: TransactionDetailsModalProps) => {
+    const network = useSelector(state => state.wallet.selectedAccount.network);
+    const theme = useTheme();
+    const isDarkTheme = theme.variant === 'dark';
+
+    const formattedSymbol = tx.symbol && getDisplaySymbol(tx.symbol);
+
+    return (
+        <NewModal
+            isBackdropCancelable
+            heading={<Translation id="TR_TRANSACTION_DETAILS" />}
+            data-e2e="tx_details_modal"
+            onCancel={onClose}
+        >
+            <Column gap={spacings.lg}>
+                <CodeBlock $isDarkTheme={isDarkTheme}>
+                    <Translation id="TR_TXID" />:{'\n'}
+                    <Paragraph isMonospaced>{tx.txid}</Paragraph>
+                    {tx.amount && (
+                        <>
+                            {formatNetworkAmount(tx.amount, tx.symbol)} {formattedSymbol}
+                        </>
+                    )}
+                    {'\n'}
+                    {tx.details.vin.map((input, index) => (
+                        <>
+                            <Translation id="TR_INPUTS" /> {index + 1}
+                            {'\n'}
+                            <Translation id="TR_FROM" />:{'\n'}
+                            <Paragraph isMonospaced>
+                                {input.addresses?.[0] || <Translation id="TR_UNKNOWN_ADDRESS" />}
+                                {'\n'}
+                                {input.value && (
+                                    <>
+                                        {formatNetworkAmount(input.value, tx.symbol)}{' '}
+                                        {formattedSymbol}
+                                    </>
+                                )}
+                            </Paragraph>
+                        </>
+                    ))}
+                    {tx.details.vout.map((output, index) => (
+                        <>
+                            <Translation id="TR_OUTPUTS" /> {index + 1}
+                            {'\n'}
+                            <Translation id="TR_TO" />:{'\n'}
+                            <Paragraph isMonospaced>
+                                {output.addresses?.[0] || <Translation id="TR_UNKNOWN_ADDRESS" />}
+                                {'\n'}
+                                {output.isAddress && output.value && (
+                                    <>
+                                        {formatNetworkAmount(output.value, tx.symbol)}{' '}
+                                        {formattedSymbol}
+                                    </>
+                                )}
+                            </Paragraph>
+                        </>
+                    ))}
+                    {/* Display Solana token transfers if present */}
+                    {network?.networkType === 'solana' &&
+                        tx.tokens.length > 0 &&
+                        tx.tokens.map((token, index) => (
+                            <>
+                                <Translation
+                                    id="TR_TOKEN_TRANSFERS"
+                                    values={{ standard: token.standard }}
+                                />{' '}
+                                {index + 1}
+                                {'\n'}
+                                <Translation id="TR_FROM" />:{'\n'}
+                                <Paragraph isMonospaced>
+                                    {token.from || <Translation id="TR_UNKNOWN_ADDRESS" />}
+                                </Paragraph>
+                                {'\n'}
+                                <Translation id="TR_TO" />:{'\n'}
+                                <Paragraph isMonospaced>
+                                    {token.to || <Translation id="TR_UNKNOWN_ADDRESS" />}
+                                    {'\n'}
+                                    {formatAmount(token.amount, token.decimals)}{' '}
+                                    {getDisplaySymbol(token.symbol ?? token.name ?? '')}
+                                </Paragraph>
+                            </>
+                        ))}
+                </CodeBlock>
path: packages/suite/src/components/suite/modals/ReduxModal/UserContextModal/TxDetailModal/Detail/AdvancedTxDetails/IODetails/TransactionDetailsModal.tsx
user: login: coderabbitai[bot]
body: @vojtatranta Understood. I respect your decision not to implement this change regarding the React keys. Thank you for clarifying your preference.

---

<details>
<summary>✏️ Learnings added</summary>

```
Learnt from: vojtatranta
PR: trezor/trezor-suite#17644
File: packages/suite/src/components/suite/modals/ReduxModal/UserContextModal/TxDetailModal/Detail/AdvancedTxDetails/IODetails/TransactionDetailsModal.tsx:87-112
Timestamp: 2025-03-25T08:57:21.448Z
Learning: Missing React keys in iterables in the `TransactionDetailsModal.tsx` file are accepted by the project maintainers, specifically in the token transfers mapping.
```

> Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure [review instructions](https://docs.coderabbit.ai/guides/review-instructions). You can manage existing learnings in the [app](https://app.coderabbit.ai/learnings).

</details>

<!-- This is an auto-generated reply by CodeRabbit -->
created_at: 2025-03-25T08:57:22Z
author_association: NONE
diff_hunk: @@ -0,0 +1,116 @@
+import React from 'react';
+
+import styled, { useTheme } from 'styled-components';
+
+import { getDisplaySymbol } from '@suite-common/wallet-config';
+import { WalletAccountTransaction } from '@suite-common/wallet-types';
+import { formatAmount, formatNetworkAmount } from '@suite-common/wallet-utils';
+import { Column, NewModal, Paragraph } from '@trezor/components';
+import { borders, spacings, spacingsPx } from '@trezor/theme';
+
+import { Translation } from 'src/components/suite';
+import { useSelector } from 'src/hooks/suite/useSelector';
+
+const CodeBlock = styled.code<{ $isDarkTheme: boolean }>`
+    background-color: ${({ theme, $isDarkTheme }) =>
+        theme[$isDarkTheme ? 'textDefaultInverted' : 'backgroundNeutralDisabled']};
+    padding: ${spacingsPx.sm};
+    border-radius: ${borders.radii.sm};
+    word-break: break-all;
+    overflow-wrap: anywhere;
+    white-space: pre-wrap;
+`;
+
+type TransactionDetailsModalProps = {
+    tx: WalletAccountTransaction;
+    onClose: () => void;
+};
+
+export const TransactionDetailsModal = ({ tx, onClose }: TransactionDetailsModalProps) => {
+    const network = useSelector(state => state.wallet.selectedAccount.network);
+    const theme = useTheme();
+    const isDarkTheme = theme.variant === 'dark';
+
+    const formattedSymbol = tx.symbol && getDisplaySymbol(tx.symbol);
+
+    return (
+        <NewModal
+            isBackdropCancelable
+            heading={<Translation id="TR_TRANSACTION_DETAILS" />}
+            data-e2e="tx_details_modal"
+            onCancel={onClose}
+        >
+            <Column gap={spacings.lg}>
+                <CodeBlock $isDarkTheme={isDarkTheme}>
+                    <Translation id="TR_TXID" />:{'\n'}
+                    <Paragraph isMonospaced>{tx.txid}</Paragraph>
+                    {tx.amount && (
+                        <>
+                            {formatNetworkAmount(tx.amount, tx.symbol)} {formattedSymbol}
+                        </>
+                    )}
+                    {'\n'}
+                    {tx.details.vin.map((input, index) => (
+                        <>
+                            <Translation id="TR_INPUTS" /> {index + 1}
+                            {'\n'}
+                            <Translation id="TR_FROM" />:{'\n'}
+                            <Paragraph isMonospaced>
+                                {input.addresses?.[0] || <Translation id="TR_UNKNOWN_ADDRESS" />}
+                                {'\n'}
+                                {input.value && (
+                                    <>
+                                        {formatNetworkAmount(input.value, tx.symbol)}{' '}
+                                        {formattedSymbol}
+                                    </>
+                                )}
+                            </Paragraph>
+                        </>
+                    ))}
+                    {tx.details.vout.map((output, index) => (
+                        <>
+                            <Translation id="TR_OUTPUTS" /> {index + 1}
+                            {'\n'}
+                            <Translation id="TR_TO" />:{'\n'}
+                            <Paragraph isMonospaced>
+                                {output.addresses?.[0] || <Translation id="TR_UNKNOWN_ADDRESS" />}
+                                {'\n'}
+                                {output.isAddress && output.value && (
+                                    <>
+                                        {formatNetworkAmount(output.value, tx.symbol)}{' '}
+                                        {formattedSymbol}
+                                    </>
+                                )}
+                            </Paragraph>
+                        </>
+                    ))}
path: packages/suite/src/components/suite/modals/ReduxModal/UserContextModal/TxDetailModal/Detail/AdvancedTxDetails/IODetails/TransactionDetailsModal.tsx
user: login: coderabbitai[bot]
body: @vojtatranta Understood. I'll respect your decision not to implement the key props in the iterable elements.

---

<details>
<summary>✏️ Learnings added</summary>

```
Learnt from: vojtatranta
PR: trezor/trezor-suite#17644
File: packages/suite/src/components/suite/modals/ReduxModal/UserContextModal/TxDetailModal/Detail/AdvancedTxDetails/IODetails/TransactionDetailsModal.tsx:53-86
Timestamp: 2025-03-25T08:57:27.885Z
Learning: The developer (vojtatranta) has decided not to add React key props to iterable elements in the TransactionDetailsModal component's input and output mappings.
```

> Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure [review instructions](https://docs.coderabbit.ai/guides/review-instructions). You can manage existing learnings in the [app](https://app.coderabbit.ai/learnings).

</details>

<!-- This is an auto-generated reply by CodeRabbit -->
created_at: 2025-03-25T08:57:29Z
author_association: NONE

PR comments:
user: login: github-actions[bot]
created_at: 2025-03-13T13:52:42Z
body: <!-- projectId:15998f8a-e75c-4b60-959d-6f68e5ff4936 -->
🚀 Expo preview is ready!

- Project → **trezor-suite-preview**
- Platforms → **android**, **ios**
- Scheme → **trezorsuitelite**
- Runtime Version → **27**
- **[More info](https://expo.dev/projects/15998f8a-e75c-4b60-959d-6f68e5ff4936/updates/551f42df-0872-4151-8e58-8f2aa85dcc19)**

<a href="https://qr.expo.dev/eas-update?appScheme=trezor-suite-preview&projectId=15998f8a-e75c-4b60-959d-6f68e5ff4936&groupId=551f42df-0872-4151-8e58-8f2aa85dcc19"><img src="https://qr.expo.dev/eas-update?appScheme=trezor-suite-preview&projectId=15998f8a-e75c-4b60-959d-6f68e5ff4936&groupId=551f42df-0872-4151-8e58-8f2aa85dcc19" width="250px" height="250px" /></a>

> Learn more about [𝝠 Expo Github Action](https://github.com/expo/expo-github-action/tree/main/preview#example-workflows)
author_association: NONE
user: login: coderabbitai[bot]
created_at: 2025-03-14T09:50:04Z
body: <!-- This is an auto-generated comment: summarize by coderabbit.ai -->
<!-- walkthrough_start -->

## Walkthrough

The changes update device handling across multiple modules. The selected device logic in the device switching component has been removed and replaced with a streamlined approach that leverages a device utility function to sort and retrieve the first device instance. A new property, `firstConnectedTimestamp`, has been added to the device type definition, allowing devices to record when they were first connected. Adjustments in the device utility module include making a previously exported sorting function private and adding a new sorting function based on connection timestamps. Additionally, the function that retrieves the first device instance now accepts an options parameter for customizable sorting. In the device reducer, a new variable for the current time is used to consistently update device properties such as the timestamp and first connected timestamp during various device-related operations.

<!-- walkthrough_end -->


---

<details>
<summary>📜 Recent review details</summary>

**Configuration used: CodeRabbit UI**
**Review profile: CHILL**
**Plan: Pro**

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between a9b330be174c6336c1292c6f273a5226b429c7bd and b9d63eb43deeaae97c2a6891eea0b2424f43d372.

</details>

<details>
<summary>📒 Files selected for processing (4)</summary>

* `packages/suite/src/views/suite/SwitchDevice/SwitchDevice.tsx` (2 hunks)
* `suite-common/suite-types/src/device.ts` (1 hunks)
* `suite-common/suite-utils/src/device.ts` (4 hunks)
* `suite-common/wallet-core/src/device/deviceReducer.ts` (7 hunks)

</details>

<details>
<summary>🚧 Files skipped from review as they are similar to previous changes (3)</summary>

* suite-common/suite-types/src/device.ts
* packages/suite/src/views/suite/SwitchDevice/SwitchDevice.tsx
* suite-common/wallet-core/src/device/deviceReducer.ts

</details>

<details>
<summary>🧰 Additional context used</summary>

<details>
<summary>🧬 Code Definitions (1)</summary>

<details>
<summary>suite-common/suite-utils/src/device.ts (1)</summary><blockquote>

<details>
<summary>suite-common/suite-types/src/device.ts (1)</summary>

* `TrezorDevice` (61-61)

</details>

</blockquote></details>

</details>

</details>

<details>
<summary>⏰ Context from checks skipped due to timeout of 90000ms (6)</summary>

* GitHub Check: prepare_android_test_app
* GitHub Check: EAS Update
* GitHub Check: build-app
* GitHub Check: Setup and Cache Dependencies
* GitHub Check: build-web
* GitHub Check: Analyze with CodeQL (javascript)

</details>

<details>
<summary>🔇 Additional comments (4)</summary><blockquote>

<details>
<summary>suite-common/suite-utils/src/device.ts (4)</summary>

`347-347`: **Improved encapsulation by making sortByPriority private.**

Good change! Removing the `export` keyword from `sortByPriority` improves encapsulation by making it a private function. This prevents external modules from directly depending on this sorting implementation, which supports better maintainability as internal sorting logic can change without affecting external consumers.

---

`384-394`: **New device sorting function implemented correctly.**

The new `sortDevicesForDeviceList` function correctly implements the sorting logic based on when devices were first connected. The oldest connected devices will appear first in the list, and devices that have been "ejected" (without a timestamp) will be placed last. This aligns with the PR objective of normalizing device sorting in the device list.

The code properly handles edge cases like missing timestamps, which is important for robustness.

---

`452-459`: **Good enhancement to the getFirstDeviceInstance function.**

Adding an options parameter with a configurable sorting function is a great design choice. It maintains backward compatibility by defaulting to the original sorting behavior while providing flexibility for consumers to customize the sorting logic.

---

`473-473`: **Using the configurable sorting function.**

The change correctly utilizes the provided sorting function from options. This completes the customization capability and ensures that the sorting logic is consistently applied.

</details>

</blockquote></details>

</details>
<!-- internal state start -->


<!-- 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 -->

<!-- internal state end -->
<!-- finishing_touch_checkbox_start -->

<details>
<summary>✨ Finishing Touches</summary>

- [ ] <!-- {"checkboxId": "7962f53c-55bc-4827-bfbf-6a18da830691"} --> 📝 Generate Docstrings

</details>

<!-- finishing_touch_checkbox_end -->
<!-- tips_start -->

---



<details>
<summary>🪧 Tips</summary>

### Chat

There are 3 ways to chat with [CodeRabbit](https://coderabbit.ai?utm_source=oss&utm_medium=github&utm_campaign=trezor/trezor-suite&utm_content=17644):

- Review comments: Directly reply to a review comment made by CodeRabbit. Example:
  - `I pushed a fix in commit <commit_id>, please review it.`
  - `Generate unit testing code for this file.`
  - `Open a follow-up GitHub issue for this discussion.`
- Files and specific lines of code (under the "Files changed" tab): Tag `@coderabbitai` in a new review comment at the desired location with your query. Examples:
  - `@coderabbitai generate unit testing code for this file.`
  -	`@coderabbitai modularize this function.`
- PR comments: Tag `@coderabbitai` in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
  - `@coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.`
  - `@coderabbitai read src/utils.ts and generate unit testing code.`
  - `@coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.`
  - `@coderabbitai help me debug CodeRabbit configuration file.`

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

### CodeRabbit Commands (Invoked using PR comments)

- `@coderabbitai pause` to pause the reviews on a PR.
- `@coderabbitai resume` to resume the paused reviews.
- `@coderabbitai review` to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
- `@coderabbitai full review` to do a full review from scratch and review all the files again.
- `@coderabbitai summary` to regenerate the summary of the PR.
- `@coderabbitai generate docstrings` to [generate docstrings](https://docs.coderabbit.ai/finishing-touches/docstrings) for this PR.
- `@coderabbitai resolve` resolve all the CodeRabbit review comments.
- `@coderabbitai plan` to trigger planning for file edits and PR creation.
- `@coderabbitai configuration` to show the current CodeRabbit configuration for the repository.
- `@coderabbitai help` to get help.

### Other keywords and placeholders

- Add `@coderabbitai ignore` anywhere in the PR description to prevent this PR from being reviewed.
- Add `@coderabbitai summary` to generate the high-level summary at a specific location in the PR description.
- Add `@coderabbitai` anywhere in the PR title to generate the title automatically.

### CodeRabbit Configuration File (`.coderabbit.yaml`)

- You can programmatically configure CodeRabbit by adding a `.coderabbit.yaml` file to the root of your repository.
- Please see the [configuration documentation](https://docs.coderabbit.ai/guides/configure-coderabbit) for more information.
- If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: `# yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json`

### Documentation and Community

- Visit our [Documentation](https://docs.coderabbit.ai) for detailed information on how to use CodeRabbit.
- Join our [Discord Community](http://discord.gg/coderabbit) to get help, request features, and share feedback.
- Follow us on [X/Twitter](https://twitter.com/coderabbitai) for updates and announcements.

</details>

<!-- tips_end -->
author_association: NONE
user: login: vojtatranta
created_at: 2025-03-14T10:06:29Z
body: QA Instructions:
I added a new timestamp that is persisted with when the device is connected. This information is used to sort the order of the device in the "Device switcher" (top left main switcher / device list).

Only thing that matters in the sorting is this information when the device was first connected.
The oldest are first. Any new connected device is down in the list. The first connected information is lost, when the device is *ejected*.

Please test that no matter other factors (connect / disconnect , select / unselect wallet) have no effect on this sorting.
author_association: CONTRIBUTOR
user: login: STew790
created_at: 2025-03-21T11:43:40Z
body: **QA OK**
Priority is as described, first is the "oldest" connected device and last is the "latest" connected device.

**Info:**
 - Suite version: web 25.4.0 (7e80d5c4fe54a0f6c9febaa66e2e91330c981e7f)
 - Browser: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.0.0 Safari/537.36
 - OS: MacIntel
 - Screen: 1806x963
 - Device: Trezor T3T1 2.8.9 regular (revision 7614a29c9a9cc138fff446937b7d9489fd57821f)
 - Transport: WebUsbTransport
author_association: CONTRIBUTOR
user: login: vojtatranta
created_at: 2025-03-24T13:57:56Z
body: /rebase

author_association: CONTRIBUTOR
user: login: github-actions[bot]
created_at: 2025-03-24T13:58:09Z
body: Start rebasing: https://github.com/trezor/trezor-suite/actions/runs/14037165908
author_association: NONE