Repository: trezor/trezor-suite
Pull request #17313: Connect settings in Suite
Author: login: martykan
Created: 2025-02-28T15:11:10Z | Merged: 2025-03-19T16:14:10Z

## Description

New "Connected apps" section in Suite for Trezor Connect and WalletConnect. 

## Screenshots:

<img width="914" alt="Screenshot 2025-03-19 at 15 34 45" src="https://github.com/user-attachments/assets/71413a75-d9a5-47ed-ae7d-780d04c8b2df" />



Diff:
diff --git a/packages/suite-desktop-ui/src/support/Router.tsx b/packages/suite-desktop-ui/src/support/Router.tsx
index 9f849127ea35..3ac9c84a26d8 100644
--- a/packages/suite-desktop-ui/src/support/Router.tsx
+++ b/packages/suite-desktop-ui/src/support/Router.tsx
@@ -7,6 +7,7 @@ import routes from 'src/constants/suite/routes';
 import { Dashboard } from 'src/views/dashboard';
 import PasswordManager from 'src/views/password-manager';
 import { SettingsCoins } from 'src/views/settings/SettingsCoins/SettingsCoins';
+import { SettingsConnectedApps } from 'src/views/settings/SettingsConnectedApps/SettingsConnectedApps';
 import { SettingsDebug } from 'src/views/settings/SettingsDebug/SettingsDebug';
 import { SettingsDevice } from 'src/views/settings/SettingsDevice/SettingsDevice';
 import { SettingsGeneral } from 'src/views/settings/SettingsGeneral/SettingsGeneral';
@@ -71,6 +72,7 @@ const components: { [key: string]: ComponentType<any> } = {
     'settings-coins': SettingsCoins,
     'settings-debug': SettingsDebug,
     'settings-device': SettingsDevice,
+    'settings-connected-apps': SettingsConnectedApps,
 };
 
 export const AppRouter = memo(() => (
diff --git a/packages/suite-web/src/support/Router.tsx b/packages/suite-web/src/support/Router.tsx
index 5842aae62903..25738a49b72a 100644
--- a/packages/suite-web/src/support/Router.tsx
+++ b/packages/suite-web/src/support/Router.tsx
@@ -148,6 +148,11 @@ const components: Record<PageName, LazyExoticComponent<ComponentType<any>>> = {
             /* webpackChunkName: "settings" */ 'src/views/settings/SettingsDevice/SettingsDevice'
         ).then(({ SettingsDevice }) => ({ default: SettingsDevice })),
     ),
+    'settings-connected-apps': lazy(() =>
+        import(
+            /* webpackChunkName: "settings" */ 'src/views/settings/SettingsConnectedApps/SettingsConnectedApps'
+        ).then(({ SettingsConnectedApps }) => ({ default: SettingsConnectedApps })),
+    ),
 };
 
 const AppRouter = () => (
diff --git a/packages/suite/src/actions/suite/storageActions.ts b/packages/suite/src/actions/suite/storageActions.ts
index 16576895e02f..54b199368f6d 100644
--- a/packages/suite/src/actions/suite/storageActions.ts
+++ b/packages/suite/src/actions/suite/storageActions.ts
@@ -464,6 +464,13 @@ export const saveEntropyCheckFail = () => async (_dispatch: Dispatch, getState:
     db.addItem('security', { devicesWithFailedEntropyCheck }, 'security', true);
 };
 
+export const saveConnectSettings = () => async (_dispatch: Dispatch, getState: GetState) => {
+    if (!(await db.isAccessible())) return;
+    const { wallet } = getState();
+
+    db.addItem('connect', { permissions: wallet.connectPopup.permissions }, 'connect', true);
+};
+
 export const removeDatabase = () => async (dispatch: Dispatch, getState: GetState) => {
     if (!(await db.isAccessible())) return;
 
diff --git a/packages/suite/src/components/settings/SettingsLayout.tsx b/packages/suite/src/components/settings/SettingsLayout.tsx
index c3da74e0f997..55dbab575b4b 100644
--- a/packages/suite/src/components/settings/SettingsLayout.tsx
+++ b/packages/suite/src/components/settings/SettingsLayout.tsx
@@ -1,6 +1,7 @@
 import { ReactNode, useMemo } from 'react';
 
 import { Column } from '@trezor/components';
+import { isDesktop } from '@trezor/env-utils';
 import { spacings } from '@trezor/theme';
 
 import { goto } from 'src/actions/suite/routerActions';
@@ -48,6 +49,14 @@ const SettingsHeader = () => {
                 'data-testid': '@settings/menu/wallet',
                 callback: () => dispatch(goto('settings-coins', { preserveParams: true })),
             },
+            {
+                id: 'settings-connected-apps',
+                title: <Translation id="TR_CONNECTED_APPS" />,
+                position: 'primary',
+                isHidden: !isDebugModeActive || !isDesktop(),
+                'data-testid': '@settings/menu/connected-apps',
+                callback: () => dispatch(goto('settings-connected-apps', { preserveParams: true })),
+            },
             {
                 id: 'settings-debug',
                 title: <Translation id="TR_DEBUG_SETTINGS" />,
diff --git a/packages/suite/src/components/suite/StatusLight.tsx b/packages/suite/src/components/suite/StatusLight.tsx
index 54c9fa625d19..90159188cbe0 100644
--- a/packages/suite/src/components/suite/StatusLight.tsx
+++ b/packages/suite/src/components/suite/StatusLight.tsx
@@ -3,7 +3,7 @@ import styled, { DefaultTheme } from 'styled-components';
 import { UIVariant } from '@trezor/components/src/config/types';
 import { CSSColor, Color } from '@trezor/theme';
 
-type StatusLightVariant = Extract<UIVariant, 'primary' | 'warning' | 'destructive'>;
+type StatusLightVariant = Extract<UIVariant, 'primary' | 'warning' | 'destructive' | 'info'>;
 
 type MapArgs = {
     $variant: StatusLightVariant;
@@ -15,6 +15,7 @@ const mapVariantToBackgroundColor = ({ $variant, theme }: MapArgs): CSSColor =>
         primary: 'backgroundPrimarySubtleOnElevation0',
         warning: 'backgroundAlertRedSubtleOnElevation0',
         destructive: 'backgroundAlertRedSubtleOnElevation0',
+        info: 'backgroundAlertBlueSubtleOnElevation0',
     };
 
     return theme[colorMap[$variant]];
@@ -25,6 +26,7 @@ const getInnerBackgroundColor = ({ $variant, theme }: MapArgs): CSSColor => {
         primary: 'backgroundPrimaryDefault',
         warning: 'backgroundAlertYellowBold',
         destructive: 'backgroundAlertRedBold',
+        info: 'textAlertBlue',
     };
 
     return theme[colorMap[$variant]];
diff --git a/packages/suite/src/components/suite/layouts/SuiteLayout/MobileMenu/MobileMenuActions.tsx b/packages/suite/src/components/suite/layouts/SuiteLayout/MobileMenu/MobileMenuActions.tsx
index 39f2d0012327..923e4e33fe30 100644
--- a/packages/suite/src/components/suite/layouts/SuiteLayout/MobileMenu/MobileMenuActions.tsx
+++ b/packages/suite/src/components/suite/layouts/SuiteLayout/MobileMenu/MobileMenuActions.tsx
@@ -101,6 +101,7 @@ export const MobileMenuActions = ({ closeMobileNavigation }: MobileMenuActionsPr
                     'settings-device',
                     'settings-coins',
                     'settings-debug',
+                    'settings-connected-apps',
                 ])}
                 icon="settings"
             />
diff --git a/packages/suite/src/components/suite/layouts/SuiteLayout/Sidebar/Navigation.tsx b/packages/suite/src/components/suite/layouts/SuiteLayout/Sidebar/Navigation.tsx
index fe34cd630763..1550f5b090ba 100644
--- a/packages/suite/src/components/suite/layouts/SuiteLayout/Sidebar/Navigation.tsx
+++ b/packages/suite/src/components/suite/layouts/SuiteLayout/Sidebar/Navigation.tsx
@@ -24,6 +24,7 @@ export const SETTINGS_ROUTES: Route['name'][] = [
     'settings-device',
     'settings-coins',
     'settings-debug',
+    'settings-connected-apps',
 ] as const;
 
 const navItems: Array<NavigationItemProps & { CustomComponent?: FC<NavigationItemProps> }> = [
diff --git a/packages/suite/src/components/suite/modals/ReduxModal/UserContextModal/ConnectPopupModal.tsx b/packages/suite/src/components/suite/modals/ReduxModal/UserContextModal/ConnectPopupModal.tsx
index e93d2a8b0f68..647cb214d4a8 100644
--- a/packages/suite/src/components/suite/modals/ReduxModal/UserContextModal/ConnectPopupModal.tsx
+++ b/packages/suite/src/components/suite/modals/ReduxModal/UserContextModal/ConnectPopupModal.tsx
@@ -1,5 +1,7 @@
+import { useState } from 'react';
+
 import { connectPopupActions, selectConnectPopupCall } from '@suite-common/connect-popup';
-import { H2, NewModal, Paragraph } from '@trezor/components';
+import { Checkbox, Column, H2, NewModal, Paragraph } from '@trezor/components';
 import { ERRORS } from '@trezor/connect';
 import { spacings } from '@trezor/theme';
 
@@ -7,12 +9,24 @@ import { Translation } from 'src/components/suite';
 import { useDispatch, useSelector } from 'src/hooks/suite';
 
 export const ConnectPopupModal = () => {
+    const [isRemembered, setIsRemembered] = useState(false);
+
     const dispatch = useDispatch();
     const popupCall = useSelector(selectConnectPopupCall);
     if (!popupCall || popupCall?.state !== 'request') return null;
 
-    const { methodTitle, confirmLabel, processName, origin } = popupCall;
-    const onConfirm = () => dispatch(connectPopupActions.approveCall());
+    const { methodTitle, confirmLabel, processName, origin, permissionTypes } = popupCall;
+    const onConfirm = () => {
+        if (isRemembered && processName && origin)
+            dispatch(
+                connectPopupActions.rememberAppPermissions({
+                    processName,
+                    origin,
+                    types: permissionTypes,
+                }),
+            );
+        dispatch(connectPopupActions.approveCall());
+    };
     const onCancel = () =>
         dispatch(connectPopupActions.rejectCall(ERRORS.TypedError('Method_Cancel')));
 
@@ -33,22 +47,37 @@ export const ConnectPopupModal = () => {
             }
             heading={<Translation id="TR_TREZOR_CONNECT" />}
         >
-            <H2>{methodTitle}</H2>
+            <Column gap={spacings.xs}>
+                <H2>{methodTitle}</H2>
 
-            {processName && (
-                <Paragraph margin={{ top: spacings.xs }}>
-                    <Translation id="TR_CONNECT_MODAL_PROCESS" /> <strong>{processName}</strong>
-                </Paragraph>
-            )}
-            {origin && (
-                <Paragraph>
-                    <Translation id="TR_CONNECT_MODAL_WEB_ORIGIN" /> <strong>{origin}</strong>
+                <Column>
+                    {processName && (
+                        <Paragraph>
+                            <Translation id="TR_CONNECT_MODAL_PROCESS" />{' '}
+                            <strong>{processName}</strong>
+                        </Paragraph>
+                    )}
+                    {origin && (
+                        <Paragraph>
+                            <Translation id="TR_CONNECT_MODAL_WEB_ORIGIN" />{' '}
+                            <strong>{origin}</strong>
+                        </Paragraph>
+                    )}
+                </Column>
+
+                <Paragraph variant="tertiary">
+                    <Translation id="TR_CONNECT_MODAL_REQUEST_DESCRIPTION" />
                 </Paragraph>
-            )}
 
-            <Paragraph variant="tertiary" margin={{ top: spacings.xs }}>
-                <Translation id="TR_CONNECT_MODAL_REQUEST_DESCRIPTION" />
-            </Paragraph>
+                {processName !== 'WalletConnect' && (
+                    <Checkbox
+                        isChecked={isRemembered}
+                        onClick={() => setIsRemembered(!isRemembered)}
+                    >
+                        <Translation id="TR_CONNECT_MODAL_REMEMBER" />
+                    </Checkbox>
+                )}
+            </Column>
         </NewModal>
     );
 };
diff --git a/packages/suite/src/middlewares/wallet/storageMiddleware.ts b/packages/suite/src/middlewares/wallet/storageMiddleware.ts
index c285e2e2f264..cb306f670a73 100644
--- a/packages/suite/src/middlewares/wallet/storageMiddleware.ts
+++ b/packages/suite/src/middlewares/wallet/storageMiddleware.ts
@@ -2,6 +2,7 @@ import { isAnyOf } from '@reduxjs/toolkit';
 import { MiddlewareAPI } from 'redux';
 
 import { analyticsActions } from '@suite-common/analytics';
+import { connectPopupActions } from '@suite-common/connect-popup';
 import { messageSystemActions } from '@suite-common/message-system';
 import { isDeviceRemembered } from '@suite-common/suite-utils';
 import { TokenManagementAction } from '@suite-common/token-definitions';
@@ -216,6 +217,15 @@ const storageMiddleware = (api: MiddlewareAPI<Dispatch, AppState>) => {
                 storageActions.removeDraft(action.payload.accountKey);
             }
 
+            if (
+                isAnyOf(
+                    connectPopupActions.rememberAppPermissions,
+                    connectPopupActions.forgetAppPermissions,
+                )(action)
+            ) {
+                storageActions.saveConnectSettings();
+            }
+
             switch (action.type) {
                 case WALLET_SETTINGS.SET_HIDE_BALANCE:
                 case walletSettingsActions.setLocalCurrency.type:
diff --git a/packages/suite/src/storage/definitions.ts b/packages/suite/src/storage/definitions.ts
index 387d0d4bac80..8adf2c54bb31 100644
--- a/packages/suite/src/storage/definitions.ts
+++ b/packages/suite/src/storage/definitions.ts
@@ -3,6 +3,7 @@ import { FieldValues } from 'react-hook-form';
 import type { DBSchema } from 'idb';
 
 import { AnalyticsState } from '@suite-common/analytics';
+import { AppRememberedPermission } from '@suite-common/connect-popup/src/connectPopupTypes';
 import type { MessageState } from '@suite-common/message-system';
 import type { DeviceWithEmptyPath, MessageSystem } from '@suite-common/suite-types';
 import { SimpleTokenStructure } from '@suite-common/token-definitions';
@@ -138,6 +139,12 @@ export interface SuiteDBSchema extends DBSchema {
             devicesWithFailedEntropyCheck: DeviceReducerState['devicesWithFailedEntropyCheck'];
         };
     };
+    connect: {
+        key: 'connect';
+        value: {
+            permissions: AppRememberedPermission[];
+        };
+    };
 }
 
 export type SuiteStorageUpdateMessage = StorageUpdateMessage<SuiteDBSchema>;
diff --git a/packages/suite/src/support/messages.ts b/packages/suite/src/support/messages.ts
index c5587669d38a..42617bf88f45 100644
--- a/packages/suite/src/support/messages.ts
+++ b/packages/suite/src/support/messages.ts
@@ -3541,6 +3541,10 @@ export default defineMessages({
         id: 'TR_DEBUG_SETTINGS',
         defaultMessage: 'Debug',
     },
+    TR_CONNECTED_APPS: {
+        id: 'TR_CONNECTED_APPS',
+        defaultMessage: 'Connected apps',
+    },
     TR_ACCOUNT_DETAILS_HEADER: {
         id: 'TR_ACCOUNT_DETAILS_HEADER',
         defaultMessage: 'Account details',
@@ -9595,6 +9599,10 @@ export default defineMessages({
         defaultMessage:
             'A third-party app is requesting to connect to your device. Do you want to allow this?',
     },
+    TR_CONNECT_MODAL_REMEMBER: {
+        id: 'TR_CONNECT_MODAL_REMEMBER',
+        defaultMessage: 'Always allow for this app',
+    },
     TR_CONNECT_MODAL_PROCESS: {
         id: 'TR_CONNECT_MODAL_PROCESS',
         defaultMessage: 'Process:',
@@ -9632,9 +9640,18 @@ export default defineMessages({
         defaultMessage:
             "We couldn't verify the authenticity of the request. Ensure you trust the source before proceeding.",
     },
-    TR_WALLETCONNECT_NEW_CONNECTION_URL: {
-        id: 'TR_WALLETCONNECT_NEW_CONNECTION_URL',
-        defaultMessage: 'New connection URL',
+    TR_WALLETCONNECT_ADD_CONNECTION: {
+        id: 'TR_WALLETCONNECT_ADD_CONNECTION',
+        defaultMessage: 'Add connection',
+    },
+    TR_WALLETCONNECT_ADD_CONNECTION_DESC: {
+        id: 'TR_WALLETCONNECT_ADD_CONNECTION_DESC',
+        defaultMessage:
+            'You can manually add connection here with a string provided by WalletConnect.',
+    },
+    TR_WALLETCONNECT_ADD_CONNECTION_PLACEHOLDER: {
+        id: 'TR_WALLETCONNECT_ADD_CONNECTION_PLACEHOLDER',
+        defaultMessage: 'WalletConnect string',
     },
     TR_WALLETCONNECT_SESSIONS: {
         id: 'TR_WALLETCONNECT_SESSIONS',
@@ -9657,4 +9674,16 @@ export default defineMessages({
         defaultMessage:
             'Trezor Bridge will soon no longer be supported. We recommend uninstalling it from your system. <a>Learn more</a>',
     },
+    TR_NO_CONNECTED_APPS: {
+        id: 'TR_NO_CONNECTED_APPS',
+        defaultMessage: 'No connected apps',
+    },
+    TR_WALLETCONNECT: {
+        id: 'TR_WALLETCONNECT',
+        defaultMessage: 'WalletConnect',
+    },
+    TR_FORGET: {
+        id: 'TR_FORGET',
+        defaultMessage: 'Forget',
+    },
 });
diff --git a/packages/suite/src/views/settings/SettingsConnectedApps/ConnectPermissions.tsx b/packages/suite/src/views/settings/SettingsConnectedApps/ConnectPermissions.tsx
new file mode 100644
index 000000000000..3eba13699aa0
--- /dev/null
+++ b/packages/suite/src/views/settings/SettingsConnectedApps/ConnectPermissions.tsx
@@ -0,0 +1,67 @@
+import { connectPopupActions, selectConnectAppPermissions } from '@suite-common/connect-popup';
+import { Badge, Column, Dropdown, IconCircle, Row, Text } from '@trezor/components';
+import { spacings } from '@trezor/theme';
+
+import { Translation } from 'src/components/suite';
+import { useDispatch, useSelector } from 'src/hooks/suite';
+
+export const ConnectPermissions = () => {
+    const dispatch = useDispatch();
+    const apps = useSelector(selectConnectAppPermissions);
+
+    if (apps.length === 0) {
+        return (
+            <Row padding={spacings.xl} justifyContent="center">
+                <Text align="center" variant="tertiary">
+                    <Translation id="TR_NO_CONNECTED_APPS" />
+                </Text>
+            </Row>
+        );
+    }
+
+    return (
+        <>
+            <Column flex="1" hasDivider>
+                {apps.map(app => (
+                    <Row key={app.origin} gap={spacings.md} padding={spacings.md}>
+                        <IconCircle
+                            name="appWindow"
+                            size={spacings.xxl}
+                            paddingType="small"
+                            variant="tertiary"
+                            hasBorder={false}
+                        />
+
+                        <Column flex="1">
+                            <Row gap={spacings.sm}>
+                                <Text>{app.origin}</Text>
+                                <Badge variant="tertiary">{app.processName}</Badge>
+                            </Row>
+                            <Text variant="tertiary">Permissions: {app.types.join(', ')}</Text>
+                        </Column>
+
+                        <Dropdown
+                            placement={{ position: 'bottom', alignment: 'end' }}
+                            items={[
+                                {
+                                    key: 'group1',
+                                    options: [
+                                        {
+                                            icon: 'xCircle',
+                                            label: <Translation id="TR_FORGET" />,
+                                            onClick: () => {
+                                                dispatch(
+                                                    connectPopupActions.forgetAppPermissions(app),
+                                                );
+                                            },
+                                        },
+                                    ],
+                                },
+                            ]}
+                        />
+                    </Row>
+                ))}
+            </Column>
+        </>
+    );
+};
diff --git a/packages/suite/src/views/settings/SettingsConnectedApps/SettingsConnectedApps.tsx b/packages/suite/src/views/settings/SettingsConnectedApps/SettingsConnectedApps.tsx
new file mode 100644
index 000000000000..853e3125474f
--- /dev/null
+++ b/packages/suite/src/views/settings/SettingsConnectedApps/SettingsConnectedApps.tsx
@@ -0,0 +1,58 @@
+import { useState } from 'react';
+
+import { Card, Column, H3, Row, Tabs } from '@trezor/components';
+import { spacings } from '@trezor/theme';
+
+import { SettingsLayout } from 'src/components/settings';
+import { Translation } from 'src/components/suite';
+
+import { ConnectPermissions } from './ConnectPermissions';
+import { WalletConnectButton } from './WalletConnectButton';
+import { WalletConnectList } from './WalletConnectList';
+
+export const SettingsConnectedApps = () => {
+    const tabs = [
+        {
+            id: 'trezor-connect',
+            title: <Translation id="TR_TREZOR_CONNECT" />,
+            component: <ConnectPermissions />,
+        },
+        {
+            id: 'walletconnect',
+            title: <Translation id="TR_WALLETCONNECT" />,
+            component: <WalletConnectList />,
+        },
+    ];
+    const [activeItemdId, setActiveItemId] = useState(tabs[0].id);
+
+    return (
+        <SettingsLayout>
+            <Column gap={spacings.md}>
+                <Row justifyContent="space-between">
+                    <H3>
+                        <Translation id="TR_CONNECTED_APPS" />
+                    </H3>
+                    <WalletConnectButton />
+                </Row>
+                <Card paddingType="none">
+                    <Column hasDivider>
+                        <Column padding={{ top: spacings.sm, left: spacings.lg }}>
+                            <Tabs size="large" activeItemId={activeItemdId} hasBorder={false}>
+                                {tabs.map(tab => (
+                                    <Tabs.Item
+                                        key={tab.id}
+                                        id={tab.id}
+                                        onClick={() => setActiveItemId(tab.id)}
+                                    >
+                                        {tab.title}
+                                    </Tabs.Item>
+                                ))}
+                            </Tabs>
+                        </Column>
+                        {tabs.find(tab => tab.id === activeItemdId)?.component}
+                    </Column>
+                </Card>
+            </Column>
+        </SettingsLayout>
+    );
+};
diff --git a/packages/suite/src/views/settings/SettingsConnectedApps/WalletConnectButton.tsx b/packages/suite/src/views/settings/SettingsConnectedApps/WalletConnectButton.tsx
new file mode 100644
index 000000000000..f9040aa2df07
--- /dev/null
+++ b/packages/suite/src/views/settings/SettingsConnectedApps/WalletConnectButton.tsx
@@ -0,0 +1,66 @@
+import { useState } from 'react';
+
+import { walletConnectPairThunk } from '@suite-common/walletconnect';
+import { Button, Input, NewModal } from '@trezor/components';
+
+import { Translation } from 'src/components/suite';
+import { useDispatch, useTranslation } from 'src/hooks/suite';
+
+export const WalletConnectButton = () => {
+    const dispatch = useDispatch();
+    const [connectionUrl, setConnectionUrl] = useState('');
+    const [modalOpened, setModalOpened] = useState(false);
+    const { translationString } = useTranslation();
+
+    const handleConnect = () => {
+        dispatch(walletConnectPairThunk({ uri: connectionUrl }));
+        setConnectionUrl(''); // Clear input after attempt
+        setModalOpened(false);
+    };
+    const onCancel = () => setModalOpened(false);
+
+    return (
+        <>
+            {modalOpened && (
+                <NewModal.Backdrop onClick={onCancel}>
+                    <NewModal.ModalBase
+                        heading={<Translation id="TR_WALLETCONNECT_ADD_CONNECTION" />}
+                        description={<Translation id="TR_WALLETCONNECT_ADD_CONNECTION_DESC" />}
+                        onCancel={onCancel}
+                        size="small"
+                        bottomContent={
+                            <>
+                                <NewModal.Button
+                                    onClick={handleConnect}
+                                    isDisabled={!connectionUrl}
+                                >
+                                    <Translation id="TR_CONNECT" />
+                                </NewModal.Button>
+                                <NewModal.Button variant="tertiary" onClick={onCancel}>
+                                    <Translation id="TR_CANCEL" />
+                                </NewModal.Button>
+                            </>
+                        }
+                    >
+                        <Input
+                            value={connectionUrl}
+                            onChange={e => setConnectionUrl(e.target.value)}
+                            placeholder={translationString(
+                                'TR_WALLETCONNECT_ADD_CONNECTION_PLACEHOLDER',
+                            )}
+                        />
+                    </NewModal.ModalBase>
+                </NewModal.Backdrop>
+            )}
+
+            <Button
+                icon="plus"
+                variant="tertiary"
+                size="small"
+                onClick={() => setModalOpened(true)}
+            >
+                <Translation id="TR_WALLETCONNECT" />
+            </Button>
+        </>
+    );
+};
diff --git a/packages/suite/src/views/settings/SettingsConnectedApps/WalletConnectList.tsx b/packages/suite/src/views/settings/SettingsConnectedApps/WalletConnectList.tsx
new file mode 100644
index 000000000000..fbd3b451259b
--- /dev/null
+++ b/packages/suite/src/views/settings/SettingsConnectedApps/WalletConnectList.tsx
@@ -0,0 +1,85 @@
+import { useState } from 'react';
+
+import styled from 'styled-components';
+
+import { selectSessions, walletConnectDisconnectThunk } from '@suite-common/walletconnect';
+import { Column, Dropdown, IconCircle, Row, Text } from '@trezor/components';
+import { spacings, spacingsPx } from '@trezor/theme';
+
+import { Translation } from 'src/components/suite';
+import { useDispatch, useSelector } from 'src/hooks/suite';
+
+const AppIconImage = styled.img`
+    width: ${spacingsPx.xxl};
+    height: ${spacingsPx.xxl};
+    border-radius: ${spacingsPx.md};
+    background: ${({ theme }) => theme.backgroundNeutralSubtleOnElevation1};
+`;
+
+export const WalletConnectList = () => {
+    const dispatch = useDispatch();
+    const sessions = useSelector(selectSessions);
+    // TODO: we need some sort of proxy to load images from 3rd parties securely
+    const [fallbackIcon, setFallbackIcon] = useState(true);
+
+    if (sessions.length === 0) {
+        return (
+            <Row padding={spacings.xl} justifyContent="center">
+                <Text align="center" variant="tertiary">
+                    <Translation id="TR_NO_CONNECTED_APPS" />
+                </Text>
+            </Row>
+        );
+    }
+
+    return (
+        <>
+            <Column flex="1" hasDivider>
+                {sessions.map(session => (
+                    <Row key={session.topic} gap={spacings.md} padding={spacings.md}>
+                        {fallbackIcon ? (
+                            <IconCircle
+                                name="appWindow"
+                                size={spacings.xxl}
+                                paddingType="small"
+                                variant="tertiary"
+                                hasBorder={false}
+                            />
+                        ) : (
+                            <AppIconImage
+                                src={session.peer.metadata.icons[0]}
+                                onError={() => setFallbackIcon(true)}
+                            />
+                        )}
+                        <Column flex="1">
+                            <Text>{session.peer.metadata.name}</Text>
+                            <Text variant="tertiary">{session.peer.metadata.url}</Text>
+                        </Column>
+
+                        <Dropdown
+                            placement={{ position: 'bottom', alignment: 'end' }}
+                            items={[
+                                {
+                                    key: 'group1',
+                                    options: [
+                                        {
+                                            icon: 'xCircle',
+                                            label: <Translation id="TR_DISCONNECT" />,
+                                            onClick: () => {
+                                                dispatch(
+                                                    walletConnectDisconnectThunk({
+                                                        topic: session.topic,
+                                                    }),
+                                                );
+                                            },
+                                        },
+                                    ],
+                                },
+                            ]}
+                        />
+                    </Row>
+                ))}
+            </Column>
+        </>
+    );
+};
diff --git a/packages/suite/src/views/settings/SettingsDebug/SettingsDebug.tsx b/packages/suite/src/views/settings/SettingsDebug/SettingsDebug.tsx
index c5628b7e9ec0..7e3e68f2603a 100644
--- a/packages/suite/src/views/settings/SettingsDebug/SettingsDebug.tsx
+++ b/packages/suite/src/views/settings/SettingsDebug/SettingsDebug.tsx
@@ -24,7 +24,6 @@ import { Transport } from './Transport';
 import { TransportBackends } from './TransportBackends';
 import { TriggerHighlight } from './TriggerHighlight';
 import { ViewOnlySettings } from './ViewOnlySettings';
-import { WalletConnect } from './WalletConnect';
 import { WipeData } from './WipeData';
 
 export const SettingsDebug = () => {
@@ -87,9 +86,6 @@ export const SettingsDebug = () => {
             <SettingsSection title="Message system info">
                 <MessageSystemDebugInfo />
             </SettingsSection>
-            <SettingsSection title="WalletConnect">
-                <WalletConnect />
-            </SettingsSection>
             {isDesktop() && (
                 <SettingsSection title={<Translation id="TR_BLUETOOTH" />}>
                     <Bluetooth />
diff --git a/packages/suite/src/views/settings/SettingsDebug/WalletConnect.tsx b/packages/suite/src/views/settings/SettingsDebug/WalletConnect.tsx
deleted file mode 100644
index 78c629830d56..000000000000
--- a/packages/suite/src/views/settings/SettingsDebug/WalletConnect.tsx
+++ /dev/null
@@ -1,77 +0,0 @@
-import { useState } from 'react';
-
-import {
-    selectSessions,
-    walletConnectDisconnectThunk,
-    walletConnectPairThunk,
-} from '@suite-common/walletconnect';
-import { Button, Column, Input, Row, Text } from '@trezor/components';
-import { spacings } from '@trezor/theme';
-
-import {
-    ActionButton,
-    ActionColumn,
-    SectionItem,
-    StatusLight,
-    TextColumn,
-    Translation,
-} from 'src/components/suite';
-import { useDispatch, useSelector, useTranslation } from 'src/hooks/suite';
-
-export const WalletConnect = () => {
-    const dispatch = useDispatch();
-    const [connectionUrl, setConnectionUrl] = useState('');
-    const sessions = useSelector(selectSessions);
-    const { translationString } = useTranslation();
-
-    const handleConnect = () => {
-        dispatch(walletConnectPairThunk({ uri: connectionUrl }));
-        setConnectionUrl(''); // Clear input after attempt
-    };
-
-    return (
-        <>
-            <SectionItem data-test="@settings/walletconnect">
-                <Row flex="1">
-                    <Input
-                        value={connectionUrl}
-                        onChange={e => setConnectionUrl(e.target.value)}
-                        size="small"
-                        placeholder={translationString('TR_WALLETCONNECT_NEW_CONNECTION_URL')}
-                    />
-                    <ActionButton onClick={handleConnect}>
-                        <Translation id="TR_CONNECT" />
-                    </ActionButton>
-                </Row>
-            </SectionItem>
-            <SectionItem>
-                <TextColumn title={<Translation id="TR_WALLETCONNECT_SESSIONS" />} />
-                <ActionColumn>
-                    <Column gap={spacings.md}>
-                        {sessions.map(session => (
-                            <Row key={session.topic} gap={spacings.md}>
-                                <StatusLight variant="primary" />
-                                <Column flex="1">
-                                    <Text>{session.peer.metadata.name}</Text>
-                                    <Text variant="tertiary">{session.peer.metadata.url}</Text>
-                                    <Text variant="tertiary">{session.topic}</Text>
-                                </Column>
-                                <Button
-                                    onClick={() => {
-                                        dispatch(
-                                            walletConnectDisconnectThunk({ topic: session.topic }),
-                                        );
-                                    }}
-                                    size="small"
-                                    variant="destructive"
-                                >
-                                    <Translation id="TR_DISCONNECT" />
-                                </Button>
-                            </Row>
-                        ))}
-                    </Column>
-                </ActionColumn>
-            </SectionItem>
-        </>
-    );
-};
diff --git a/suite-common/connect-popup/src/connectPopupActions.ts b/suite-common/connect-popup/src/connectPopupActions.ts
index abbf76cc5087..2567f48ff5df 100644
--- a/suite-common/connect-popup/src/connectPopupActions.ts
+++ b/suite-common/connect-popup/src/connectPopupActions.ts
@@ -1,6 +1,6 @@
 import { createAction } from '@reduxjs/toolkit';
 
-import { ConnectPopupCall } from './connectPopupTypes';
+import { AppRememberedPermission, ConnectPopupCall } from './connectPopupTypes';
 
 export const ACTION_PREFIX = '@suite-common/connect-popup';
 
@@ -16,9 +16,25 @@ const rejectCall = createAction(`${ACTION_PREFIX}/rejectCall`, (payload: Error)
     payload,
 }));
 
+const rememberAppPermissions = createAction(
+    `${ACTION_PREFIX}/rememberAppPermissions`,
+    (payload: AppRememberedPermission) => ({
+        payload,
+    }),
+);
+
+const forgetAppPermissions = createAction(
+    `${ACTION_PREFIX}/forgetAppPermissions`,
+    (payload: AppRememberedPermission) => ({
+        payload,
+    }),
+);
+
 export const connectPopupActions = {
     initiateCall,
     approveCall,
     finishCall,
     rejectCall,
+    rememberAppPermissions,
+    forgetAppPermissions,
 } as const;
diff --git a/suite-common/connect-popup/src/connectPopupReducer.ts b/suite-common/connect-popup/src/connectPopupReducer.ts
index e0a09a0d5942..6c519e9acf2a 100644
--- a/suite-common/connect-popup/src/connectPopupReducer.ts
+++ b/suite-common/connect-popup/src/connectPopupReducer.ts
@@ -1,10 +1,11 @@
 import { createReducerWithExtraDeps } from '@suite-common/redux-utils';
 
 import { connectPopupActions } from './connectPopupActions';
-import { ConnectPopupCall } from './connectPopupTypes';
+import { AppRememberedPermission, ConnectPopupCall } from './connectPopupTypes';
 
 export type ConnectPopupState = {
     activeCall?: ConnectPopupCall;
+    permissions: AppRememberedPermission[];
 };
 
 type ConnectPopupStateRootState = {
@@ -13,6 +14,7 @@ type ConnectPopupStateRootState = {
 
 const connectPopupInitialState: ConnectPopupState = {
     activeCall: undefined,
+    permissions: [],
 };
 
 export const prepareConnectPopupReducer = createReducerWithExtraDeps(
@@ -33,9 +35,19 @@ export const prepareConnectPopupReducer = createReducerWithExtraDeps(
                 if (state.activeCall?.state === 'request')
                     state.activeCall.confirmation.reject(payload);
                 state.activeCall = undefined;
+            })
+            .addCase(connectPopupActions.rememberAppPermissions, (state, { payload }) => {
+                state.permissions = state.permissions.filter(p => p.origin !== payload.origin);
+                state.permissions.push(payload);
+            })
+            .addCase(connectPopupActions.forgetAppPermissions, (state, { payload }) => {
+                state.permissions = state.permissions.filter(p => p.origin !== payload.origin);
             });
     },
 );
 
 export const selectConnectPopupCall = (state: ConnectPopupStateRootState) =>
     state.wallet.connectPopup.activeCall;
+
+export const selectConnectAppPermissions = (state: ConnectPopupStateRootState) =>
+    state.wallet.connectPopup.permissions;
diff --git a/suite-common/connect-popup/src/connectPopupThunks.ts b/suite-common/connect-popup/src/connectPopupThunks.ts
index 6d655a7b5d2f..8e6e76c39535 100644
--- a/suite-common/connect-popup/src/connectPopupThunks.ts
+++ b/suite-common/connect-popup/src/connectPopupThunks.ts
@@ -4,10 +4,12 @@ import { CustomThunkAPI, createThunk } from '@suite-common/redux-utils';
 import { deviceActions, selectSelectedDevice } from '@suite-common/wallet-core';
 import TrezorConnect, { CallMethodParams, CallMethodResponse, ERRORS } from '@trezor/connect';
 import { TypedError, serializeError } from '@trezor/connect/src/constants/errors';
+import { MethodPermission } from '@trezor/connect/src/core/AbstractMethod';
 import { DEEPLINK_VERSION } from '@trezor/connect/src/data/version';
 import { createDeferred } from '@trezor/utils';
 
 import { connectPopupActions } from './connectPopupActions';
+import { selectConnectAppPermissions } from './connectPopupReducer';
 
 const CONNECT_POPUP_MODULE = '@common/connect-popup';
 
@@ -58,20 +60,35 @@ export const connectPopupCallThunkInner = createThunk<
                 return;
             }
 
-            const confirmation = createDeferred();
-            dispatch(extra.actions.lockDevice(true));
-            dispatch(
-                connectPopupActions.initiateCall({
-                    state: 'request',
-                    method,
-                    methodTitle: methodInfo.payload.confirmation?.label ?? methodInfo.payload.info,
-                    confirmLabel: methodInfo.payload.confirmation?.customConfirmButton?.label,
-                    processName,
-                    origin,
-                    confirmation,
-                }),
+            // Check if permission remembered
+            const rememberedApps = selectConnectAppPermissions(getState());
+            const isRemembered = rememberedApps.some(
+                app =>
+                    app.origin === origin &&
+                    app.processName === processName &&
+                    methodInfo.payload.requiredPermissions.every((permission: MethodPermission) =>
+                        app.types.includes(permission),
+                    ),
             );
-            await confirmation.promise;
+
+            if (!isRemembered) {
+                const confirmation = createDeferred();
+                dispatch(extra.actions.lockDevice(true));
+                dispatch(
+                    connectPopupActions.initiateCall({
+                        state: 'request',
+                        method,
+                        methodTitle:
+                            methodInfo.payload.confirmation?.label ?? methodInfo.payload.info,
+                        confirmLabel: methodInfo.payload.confirmation?.customConfirmButton?.label,
+                        processName,
+                        origin,
+                        confirmation,
+                        permissionTypes: methodInfo.payload.requiredPermissions,
+                    }),
+                );
+                await confirmation.promise;
+            }
 
             const device = selectSelectedDevice(getState());
             if (!device) {
diff --git a/suite-common/connect-popup/src/connectPopupTypes.ts b/suite-common/connect-popup/src/connectPopupTypes.ts
index fb2209b6bbab..a86538bd1642 100644
--- a/suite-common/connect-popup/src/connectPopupTypes.ts
+++ b/suite-common/connect-popup/src/connectPopupTypes.ts
@@ -1,4 +1,5 @@
 import { TrezorError } from '@trezor/connect/src/constants/errors';
+import { MethodPermission } from '@trezor/connect/src/core/AbstractMethod';
 import { Deferred } from '@trezor/utils';
 
 export type ConnectPopupCall =
@@ -9,6 +10,7 @@ export type ConnectPopupCall =
           confirmLabel: string;
           processName?: string;
           origin?: string;
+          permissionTypes: MethodPermission[];
           confirmation: Deferred<void>;
       }
     | {
@@ -22,3 +24,9 @@ export type ConnectPopupCall =
     | {
           state: 'finished';
       };
+
+export type AppRememberedPermission = {
+    origin: string;
+    processName: string;
+    types: MethodPermission[];
+};
diff --git a/suite-common/suite-config/src/routes.ts b/suite-common/suite-config/src/routes.ts
index e6bb2baece8a..b8e340247a80 100644
--- a/suite-common/suite-config/src/routes.ts
+++ b/suite-common/suite-config/src/routes.ts
@@ -97,6 +97,11 @@ export const routes = [
         pattern: '/settings/coins',
         app: 'settings',
     },
+    {
+        name: 'settings-connected-apps',
+        pattern: '/settings/connected-apps',
+        app: 'settings',
+    },
     {
         name: 'recovery-index',
         pattern: '/recovery',
diff --git a/suite-common/walletconnect/src/walletConnectThunks.ts b/suite-common/walletconnect/src/walletConnectThunks.ts
index 113b67fb3453..32725efacbaf 100644
--- a/suite-common/walletconnect/src/walletConnectThunks.ts
+++ b/suite-common/walletconnect/src/walletConnectThunks.ts
@@ -220,6 +220,7 @@ export const sessionProposalApproveThunk = createThunk<
             dispatch(
                 walletConnectActions.saveSession({
                     ...session,
+                    validation: pendingProposal.validation,
                 }),
             );
         } catch (error) {
diff --git a/suite-common/walletconnect/src/walletConnectTypes.ts b/suite-common/walletconnect/src/walletConnectTypes.ts
index d1368599f760..294467b96ea8 100644
--- a/suite-common/walletconnect/src/walletConnectTypes.ts
+++ b/suite-common/walletconnect/src/walletConnectTypes.ts
@@ -20,6 +20,7 @@ export interface WalletConnectNamespace {
 
 export interface WalletConnectSession {
     topic: string;
+    validation?: 'VALID' | 'INVALID' | 'UNKNOWN';
     pairingTopic: string;
     expiry: number;
     acknowledged: boolean;


Reviews:
user: login: coderabbitai[bot]
body: **Actionable comments posted: 3**

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

<details>
<summary>suite-common/walletconnect/src/walletConnectTypes.ts (1)</summary><blockquote>

`23-23`: **Good addition of validation field to WalletConnectSession interface**

The new `validation` field is a good addition that will enable tracking the validation state of WalletConnect sessions, which aligns with the PR objective of creating a "Connected apps" section to manage connections.

However, I noticed that the order of union values differs from the similar field in the `PendingConnectionProposal` interface (line 55):
- Here: `'VALID' | 'INVALID' | 'UNKNOWN'`
- Line 55: `'UNKNOWN' | 'VALID' | 'INVALID'`


Consider standardizing the order of union values across both interfaces for consistency and better maintainability:

```diff
-    validation: 'VALID' | 'INVALID' | 'UNKNOWN';
+    validation: 'UNKNOWN' | 'VALID' | 'INVALID';
```

</blockquote></details>
<details>
<summary>packages/suite/src/views/settings/SettingsConnectedApps/WalletConnectButton.tsx (2)</summary><blockquote>

`22-54`: **Accessible modal implementation**

The modal implementation is well-structured with appropriate translations, but consider adding autofocus to the input field for better user experience.

```diff
 <Input
+    autoFocus
     value={connectionUrl}
     onChange={e => setConnectionUrl(e.target.value)}
     placeholder={translationString(
         'TR_WALLETCONNECT_ADD_CONNECTION_PLACEHOLDER',
     )}
 />
```

---

`45-51`: **Consider adding basic URL validation**

While the WalletConnect URI format validation might be handled in the thunk, adding basic validation in the UI can provide immediate feedback to users.

```diff
 <Input
     value={connectionUrl}
     onChange={e => setConnectionUrl(e.target.value)}
     placeholder={translationString(
         'TR_WALLETCONNECT_ADD_CONNECTION_PLACEHOLDER',
     )}
+    state={connectionUrl && !connectionUrl.startsWith('wc:') ? 'error' : undefined}
+    bottomText={connectionUrl && !connectionUrl.startsWith('wc:') 
+        ? translationString('TR_WALLETCONNECT_INVALID_URL') 
+        : undefined}
 />
```

You would need to add the "TR_WALLETCONNECT_INVALID_URL" translation string to your translation files.

</blockquote></details>
<details>
<summary>packages/suite/src/views/settings/SettingsConnectedApps/SettingsConnectedApps.tsx (2)</summary><blockquote>

`26-26`: **Verify tab state initialization**

There's a small typo in the state variable name `activeItemdId` (should be `activeItemId`). While it works as is since the same variable name is used consistently, it would be cleaner to fix this.


```diff
-    const [activeItemdId, setActiveItemId] = useState(tabs[0].id);
+    const [activeItemId, setActiveItemId] = useState(tabs[0].id);
```

---

`52-52`: **Verify tab component rendering**

When using `find()` to locate the active tab component, consider adding a fallback in case no matching tab is found. While unlikely in this controlled environment, it would improve robustness.


```diff
-                        {tabs.find(tab => tab.id === activeItemdId)?.component}
+                        {tabs.find(tab => tab.id === activeItemdId)?.component || tabs[0].component}
```

</blockquote></details>
<details>
<summary>packages/suite/src/views/settings/SettingsConnectedApps/ConnectPermissions.tsx (2)</summary><blockquote>

`40-40`: **Consider improving permission display format.**

The permissions are currently displayed as a comma-separated list, which might not be user-friendly if the permission identifiers are technical terms. Consider mapping these to human-readable descriptions or adding translations.


```diff
-<Text variant="tertiary">Permissions: {app.types.join(', ')}</Text>
+<Text variant="tertiary">
+  <Translation id="TR_PERMISSIONS" />: {app.types.map(type => (
+    <Translation key={type} id={`TR_PERMISSION_${type}`} />
+  )).reduce((prev, curr) => [prev, ', ', curr])}
+</Text>
```

---

`43-61`: **Add confirmation dialog before forgetting permissions.**

Consider implementing a confirmation dialog before removing app permissions to prevent accidental removals, especially since this action could disrupt the user's workflow with external applications.


```diff
<Dropdown
    placement={{ position: 'bottom', alignment: 'end' }}
    items={[
        {
            key: 'group1',
            options: [
                {
                    icon: 'xCircle',
                    label: <Translation id="TR_FORGET" />,
                    onClick: () => {
+                       // Show confirmation dialog before forgetting
+                       if (window.confirm(
+                           `Are you sure you want to forget permissions for ${app.origin}?`
+                       )) {
                            dispatch(
                                connectPopupActions.forgetAppPermissions(app),
                            );
+                       }
                    },
                },
            ],
        },
    ]}
/>
```

</blockquote></details>
<details>
<summary>packages/suite/src/views/settings/SettingsConnectedApps/WalletConnectList.tsx (1)</summary><blockquote>

`59-79`: **Add confirmation dialog before disconnecting sessions.**

Consider implementing a confirmation dialog before disconnecting a session to prevent accidental disconnections, as this would disrupt the user's connection to external applications.


```diff
<Dropdown
    placement={{ position: 'bottom', alignment: 'end' }}
    items={[
        {
            key: 'group1',
            options: [
                {
                    icon: 'xCircle',
                    label: <Translation id="TR_DISCONNECT" />,
                    onClick: () => {
+                       // Show confirmation dialog before disconnecting
+                       if (window.confirm(
+                           `Are you sure you want to disconnect from ${session.peer.metadata.name}?`
+                       )) {
                            dispatch(
                                walletConnectDisconnectThunk({
                                    topic: session.topic,
                                }),
                            );
+                       }
                    },
                },
            ],
        },
    ]}
/>
```

</blockquote></details>
<details>
<summary>packages/suite/src/components/suite/modals/ReduxModal/UserContextModal/ConnectPopupModal.tsx (2)</summary><blockquote>

`18-29`: **Consider additional validation for permission types.**

The current implementation correctly checks for `isRemembered && processName && origin` before dispatching the remember permissions action. However, there's no validation that `permissionTypes` exists or is non-empty.

```diff
-    const onConfirm = () => {
-        if (isRemembered && processName && origin)
+    const onConfirm = () => {
+        if (isRemembered && processName && origin && permissionTypes?.length)
             dispatch(
                 connectPopupActions.rememberAppPermissions({
                     processName,
                     origin,
                     types: permissionTypes,
                 }),
             );
         dispatch(connectPopupActions.approveCall());
     };
```

---

`72-79`: **Consider showing more details about permissions being remembered.**

The checkbox allows users to remember permissions, but it doesn't show what specific permissions will be remembered. Consider adding a tooltip or expanding the UI to display the specific permissions being granted.


Also, consider adding a conditional check to only show the checkbox when both `processName` and `origin` are present, since these are required for remembering permissions.

```diff
-{processName !== 'WalletConnect' && (
+{processName !== 'WalletConnect' && processName && origin && (
     <Checkbox
         isChecked={isRemembered}
         onClick={() => setIsRemembered(!isRemembered)}
     >
         <Translation id="TR_CONNECT_MODAL_REMEMBER" />
+        {permissionTypes?.length > 0 && (
+            <Tooltip content={permissionTypes.join(', ')}>
+                <InfoIcon size="tiny" />
+            </Tooltip>
+        )}
     </Checkbox>
)}
```

</blockquote></details>
<details>
<summary>suite-common/connect-popup/src/connectPopupThunks.ts (1)</summary><blockquote>

`74-91`: **Consider adding permission expiration mechanism.**

While the implementation correctly skips confirmation for remembered apps, there's no mechanism for expiring permissions after a certain time period or number of uses. This could be a security concern for sensitive operations.


Consider adding an expiration timestamp or usage counter to the `AppRememberedPermission` type and checking it here before skipping the confirmation dialog.

</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 c30ec82395863b73b4c665644c6f35943dd52fe5 and fd7141a0d321881696ce8abaad601513dce1a9e4.

</details>

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

* `packages/suite-desktop-ui/src/support/Router.tsx` (2 hunks)
* `packages/suite-web/src/support/Router.tsx` (1 hunks)
* `packages/suite/src/actions/suite/storageActions.ts` (1 hunks)
* `packages/suite/src/components/settings/SettingsLayout.tsx` (1 hunks)
* `packages/suite/src/components/suite/StatusLight.tsx` (3 hunks)
* `packages/suite/src/components/suite/layouts/SuiteLayout/MobileMenu/MobileMenuActions.tsx` (1 hunks)
* `packages/suite/src/components/suite/layouts/SuiteLayout/Sidebar/Navigation.tsx` (1 hunks)
* `packages/suite/src/components/suite/modals/ReduxModal/UserContextModal/ConnectPopupModal.tsx` (2 hunks)
* `packages/suite/src/middlewares/wallet/storageMiddleware.ts` (2 hunks)
* `packages/suite/src/storage/definitions.ts` (2 hunks)
* `packages/suite/src/support/messages.ts` (4 hunks)
* `packages/suite/src/views/settings/SettingsConnectedApps/ConnectPermissions.tsx` (1 hunks)
* `packages/suite/src/views/settings/SettingsConnectedApps/SettingsConnectedApps.tsx` (1 hunks)
* `packages/suite/src/views/settings/SettingsConnectedApps/WalletConnectButton.tsx` (1 hunks)
* `packages/suite/src/views/settings/SettingsConnectedApps/WalletConnectList.tsx` (1 hunks)
* `packages/suite/src/views/settings/SettingsDebug/SettingsDebug.tsx` (0 hunks)
* `packages/suite/src/views/settings/SettingsDebug/WalletConnect.tsx` (0 hunks)
* `suite-common/connect-popup/src/connectPopupActions.ts` (2 hunks)
* `suite-common/connect-popup/src/connectPopupReducer.ts` (3 hunks)
* `suite-common/connect-popup/src/connectPopupThunks.ts` (2 hunks)
* `suite-common/connect-popup/src/connectPopupTypes.ts` (3 hunks)
* `suite-common/suite-config/src/routes.ts` (1 hunks)
* `suite-common/walletconnect/src/walletConnectThunks.ts` (1 hunks)
* `suite-common/walletconnect/src/walletConnectTypes.ts` (1 hunks)

</details>

<details>
<summary>💤 Files with no reviewable changes (2)</summary>

* packages/suite/src/views/settings/SettingsDebug/SettingsDebug.tsx
* packages/suite/src/views/settings/SettingsDebug/WalletConnect.tsx

</details>

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

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

<details>
<summary>packages/suite-web/src/support/Router.tsx (1)</summary><blockquote>

<details>
<summary>packages/suite/src/views/settings/SettingsConnectedApps/SettingsConnectedApps.tsx (1) (1)</summary>

* `SettingsConnectedApps` (13-58)

</details>

</blockquote></details>
<details>
<summary>packages/suite-desktop-ui/src/support/Router.tsx (1)</summary><blockquote>

<details>
<summary>packages/suite/src/views/settings/SettingsConnectedApps/SettingsConnectedApps.tsx (1) (1)</summary>

* `SettingsConnectedApps` (13-58)

</details>

</blockquote></details>
<details>
<summary>packages/suite/src/middlewares/wallet/storageMiddleware.ts (1)</summary><blockquote>

<details>
<summary>suite-common/connect-popup/src/connectPopupActions.ts (1) (1)</summary>

* `connectPopupActions` (33-40)

</details>

</blockquote></details>
<details>
<summary>packages/suite/src/views/settings/SettingsConnectedApps/WalletConnectList.tsx (1)</summary><blockquote>

<details>
<summary>suite-common/walletconnect/src/walletConnectThunks.ts (1) (1)</summary>

* `walletConnectDisconnectThunk` (371-377)

</details>

</blockquote></details>
<details>
<summary>suite-common/connect-popup/src/connectPopupActions.ts (1)</summary><blockquote>

<details>
<summary>suite-common/connect-popup/src/connectPopupTypes.ts (1) (1)</summary>

* `AppRememberedPermission` (28-34)

</details>

</blockquote></details>
<details>
<summary>packages/suite/src/views/settings/SettingsConnectedApps/SettingsConnectedApps.tsx (4)</summary><blockquote>

<details>
<summary>packages/suite/src/views/settings/SettingsConnectedApps/ConnectPermissions.tsx (1) (1)</summary>

* `ConnectPermissions` (8-67)

</details>
<details>
<summary>packages/suite/src/views/settings/SettingsConnectedApps/WalletConnectList.tsx (1) (1)</summary>

* `WalletConnectList` (19-85)

</details>
<details>
<summary>packages/suite/src/components/settings/SettingsLayout.tsx (1) (1)</summary>

* `SettingsLayout` (79-94)

</details>
<details>
<summary>packages/suite/src/views/settings/SettingsConnectedApps/WalletConnectButton.tsx (1) (1)</summary>

* `WalletConnectButton` (9-66)

</details>

</blockquote></details>
<details>
<summary>suite-common/connect-popup/src/connectPopupReducer.ts (2)</summary><blockquote>

<details>
<summary>suite-common/connect-popup/src/connectPopupTypes.ts (2) (2)</summary>

* `ConnectPopupCall` (5-26)
* `AppRememberedPermission` (28-34)

</details>
<details>
<summary>suite-common/connect-popup/src/connectPopupActions.ts (1) (1)</summary>

* `connectPopupActions` (33-40)

</details>

</blockquote></details>
<details>
<summary>suite-common/connect-popup/src/connectPopupThunks.ts (2)</summary><blockquote>

<details>
<summary>suite-common/connect-popup/src/connectPopupReducer.ts (1) (1)</summary>

* `selectConnectAppPermissions` (52-53)

</details>
<details>
<summary>suite-common/connect-popup/src/connectPopupActions.ts (1) (1)</summary>

* `connectPopupActions` (33-40)

</details>

</blockquote></details>
<details>
<summary>packages/suite/src/components/suite/modals/ReduxModal/UserContextModal/ConnectPopupModal.tsx (2)</summary><blockquote>

<details>
<summary>suite-common/connect-popup/src/connectPopupReducer.ts (1) (1)</summary>

* `selectConnectPopupCall` (49-50)

</details>
<details>
<summary>suite-common/connect-popup/src/connectPopupActions.ts (1) (1)</summary>

* `connectPopupActions` (33-40)

</details>

</blockquote></details>
<details>
<summary>packages/suite/src/views/settings/SettingsConnectedApps/WalletConnectButton.tsx (1)</summary><blockquote>

<details>
<summary>suite-common/walletconnect/src/walletConnectThunks.ts (1) (1)</summary>

* `walletConnectPairThunk` (353-369)

</details>

</blockquote></details>

</details>

</details>

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

* GitHub Check: run-e2e-suite-web-tests (@group=metadata2, trezor-user-env-unix)
* GitHub Check: Linting and formatting
* GitHub Check: Type Checking
* GitHub Check: Unit Tests
* GitHub Check: run-e2e-suite-desktop-tests (@group=settings, trezor-user-env-unix bitcoin-regtest)
* GitHub Check: prepare_android_test_app
* GitHub Check: Analyze with CodeQL (javascript)

</details>

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

<details>
<summary>suite-common/walletconnect/src/walletConnectThunks.ts (1)</summary>

`223-223`: <details>
<summary>❓ Verification inconclusive</summary>

**Validation state correctly propagated from proposal to session**

This change correctly propagates the validation state from the pending proposal to the saved session, which is consistent with the interface change in `walletConnectTypes.ts`.



I notice that when populating active sessions (lines 337-344), there's no explicit handling for the `validation` property. This could result in undefined validation values for existing sessions that were created before this property was added.

---

🏁 Script executed:

```shell
#!/bin/bash
# Check how active sessions are saved and verify if validation is handled for existing sessions

# Find where sessions are saved or updated in the codebase
rg -A 3 -B 3 "walletConnectActions.saveSession" --type ts
```

Length of output: 1975

---



</details>

**Attention: Active sessions may lack explicit validation handling**

- The change correctly propagates the `validation` property from pending proposals when saving new sessions (line 223).
- However, when populating active sessions (lines 337-344 in `walletConnectThunks.ts`), the action is invoked with a simple spread (`{ ...sessions[topic] }`) and does not explicitly assign the `validation` property.
- As a result, sessions that were created before the addition of the validation property could end up with an undefined `validation` value.
- Please verify if these legacy sessions should default to a specific validation state or if additional handling is necessary when loading active sessions.

</details>
<details>
<summary>packages/suite/src/components/suite/layouts/SuiteLayout/MobileMenu/MobileMenuActions.tsx (1)</summary>

`104-104`: **Adding new route to active settings check - looks good**

The addition of 'settings-connected-apps' to the array of routes checked for active status is consistent with the existing pattern and ensures the settings menu item will be properly highlighted when navigating to the connected apps section.

</details>
<details>
<summary>suite-common/suite-config/src/routes.ts (1)</summary>

`100-104`: **New route for connected apps settings - LGTM**

The new route follows the established pattern for settings routes, with the appropriate name, pattern, and app association. The placement within the routes array is logical, positioned among other settings routes.

</details>
<details>
<summary>packages/suite/src/components/suite/layouts/SuiteLayout/Sidebar/Navigation.tsx (1)</summary>

`27-27`: **Addition to SETTINGS_ROUTES - looks good**

The new route has been properly added to the SETTINGS_ROUTES constant, ensuring that the Settings navigation item will be marked as active when viewing the connected apps section.

</details>
<details>
<summary>packages/suite/src/storage/definitions.ts (2)</summary>

`6-6`: **Imported AppRememberedPermission type - LGTM**

The import is correctly added to support the new database schema property.

---

`142-147`: **New database schema for storing connect permissions - LGTM**

The new `connect` property in the SuiteDBSchema follows the established pattern of other schema properties and provides appropriate structure for storing app permissions. This enables persistence of connected app permissions across sessions.

</details>
<details>
<summary>packages/suite-desktop-ui/src/support/Router.tsx (2)</summary>

`10-10`: **Clean import of the SettingsConnectedApps component**

The import statement is correctly added, following the same pattern as other settings components in the file.

---

`75-75`: **Successfully registered the SettingsConnectedApps route**

The new route is properly added to the components object, maintaining consistency with the existing route registration pattern.

</details>
<details>
<summary>packages/suite-web/src/support/Router.tsx (1)</summary>

`151-155`: **Well-implemented lazy loading for the SettingsConnectedApps component**

The implementation correctly uses lazy loading with the appropriate webpack chunk name, following the same pattern as other settings components. This ensures the component is only loaded when needed, improving initial load performance.

</details>
<details>
<summary>packages/suite/src/middlewares/wallet/storageMiddleware.ts (2)</summary>

`4-5`: **Added necessary import for connect popup actions**

The import is correctly added to access the required action types.

---

`220-227`: <details>
<summary>✅ Verification successful</summary>

**Proper persistence implementation for app permissions**

The middleware correctly detects when app permissions are modified and triggers storage actions to persist these changes, following the same pattern as other persistent data in the application.


---

🏁 Script executed:

```shell
#!/bin/bash
# Check if saveConnectSettings function is properly implemented

rg -A 10 "saveConnectSettings" --glob "*.ts" --glob "*.tsx"
```

Length of output: 2173

---



</details>

**Actionable: Verify and confirm persistence for app permissions.**

The middleware now correctly detects changes in app permissions by calling `storageActions.saveConnectSettings()`, and the implementation of this function (located in `packages/suite/src/actions/suite/storageActions.ts`) properly persists the app permissions data to the database. There are no issues identified with the persistence mechanism, as the same pattern is used consistently across the application.

</details>
<details>
<summary>packages/suite/src/views/settings/SettingsConnectedApps/WalletConnectButton.tsx (4)</summary>

`1-8`: **Well-structured imports**

The imports are organized logically, separating React, external dependencies, and internal components.

---

`9-14`: **Clean component setup with proper state management**

The component correctly uses React hooks for state management and accesses the dispatch function and translations.

---

`15-21`: **Well-implemented connection handling**

The connection handler dispatches the appropriate action, clears the input, and closes the modal. The cancel function is also implemented correctly.

---

`56-64`: **Well-implemented button for opening the modal**

The button implementation is clean and follows the design pattern of the application.

</details>
<details>
<summary>packages/suite/src/actions/suite/storageActions.ts (1)</summary>

`467-472`: **New storage action to persist connect permissions**

The new `saveConnectSettings` function follows the established pattern for database operations in this file. It appropriately checks database accessibility before attempting to store data and retrieves the necessary state from Redux.

</details>
<details>
<summary>suite-common/connect-popup/src/connectPopupTypes.ts (3)</summary>

`2-2`: **LGTM - Required import for type safety**

The import of `MethodPermission` type from Trezor Connect's core module is necessary for the permission management feature.

---

`13-13`: **LGTM - Permission types tracking enhances security**

Adding the `permissionTypes` field to track what permissions are requested by the app is a good security enhancement.

---

`28-34`: **Well-structured type for app permissions storage**

This new type provides a clear structure for storing application permission data, including app metadata and granted permissions. The types are comprehensive and support both Trezor Connect and WalletConnect implementations.

</details>
<details>
<summary>packages/suite/src/components/suite/StatusLight.tsx (3)</summary>

`6-6`: **LGTM - Added info variant for status indicators**

Adding the 'info' variant extends the component's capabilities to represent different states of connected apps.

---

`18-18`: **LGTM - Consistent background color for info variant**

The background color mapping for the 'info' variant follows the established pattern of using semantic color tokens.

---

`29-29`: **LGTM - Inner color mapping for info variant**

The inner color mapping for the 'info' variant is appropriately defined, completing the visual styling.

</details>
<details>
<summary>packages/suite/src/views/settings/SettingsConnectedApps/SettingsConnectedApps.tsx (3)</summary>

`13-26`: **Well-structured tabbed interface for connected apps**

The tabs implementation cleanly separates the two different types of connections (Trezor Connect and WalletConnect) with appropriate components for each. This maintains a consistent UI pattern with other settings pages.

---

`29-36`: **LGTM - Clean header with appropriate actions**

The header layout with the title and the WalletConnect button is well-organized. The button placement provides good discoverability for the add connection action.

---

`37-54`: **LGTM - Well-structured card and tabs layout**

The card layout with nested columns and tabs follows the established UI patterns. The tabs implementation with click handlers and conditional content rendering is clean and effective.

</details>
<details>
<summary>packages/suite/src/components/settings/SettingsLayout.tsx (1)</summary>

`51-58`: **LGTM: New Connected Apps settings menu entry.**

The addition of the Connected Apps navigation item is implemented correctly, following the established pattern for settings pages. It's notable that this feature is currently gated behind the debug mode flag, which is consistent with the existing pattern for testing features before broader release.

</details>
<details>
<summary>packages/suite/src/views/settings/SettingsConnectedApps/ConnectPermissions.tsx (1)</summary>

`8-20`: **LGTM: Empty state handling looks good.**

The component properly handles the case when no connected apps are present by displaying an appropriate message.

</details>
<details>
<summary>suite-common/connect-popup/src/connectPopupActions.ts (3)</summary>

`3-3`: **LGTM: Appropriate import of the AppRememberedPermission type.**

The action creators now correctly import the type needed for the payload.

---

`19-31`: **LGTM: Well-structured action creators.**

The new action creators for managing app permissions follow the existing pattern and are well-implemented using Redux Toolkit's createAction.

---

`38-39`: **LGTM: Updated exports.**

The actions are properly exported in the connectPopupActions object.

</details>
<details>
<summary>packages/suite/src/components/suite/modals/ReduxModal/UserContextModal/ConnectPopupModal.tsx (2)</summary>

`12-13`: **Good implementation of permission persistence functionality.**

Adding the state variable for remembering app permissions is a good approach as it allows users to avoid having to repeatedly approve permissions for the same app.

---

`50-70`: **Improved layout with Column component.**

Using the Column component for layout and structuring the modal content improves readability and maintainability.

</details>
<details>
<summary>suite-common/connect-popup/src/connectPopupThunks.ts (3)</summary>

`7-7`: **Added imports for permission handling.**

The new imports for `MethodPermission` and `selectConnectAppPermissions` are necessary to support the permission checking functionality.



Also applies to: 12-12

---

`63-72`: **Well-implemented permission checking logic.**

The permission checking logic is thorough, verifying not only if the app (by origin and processName) is remembered but also if all required permissions are already granted.

---

`87-87`: **LGTM: Adding permissionTypes to initiateCall.**

Adding the `permissionTypes` to the initiateCall action payload is a good change that allows the modal to display and remember the specific permissions being requested.

</details>
<details>
<summary>suite-common/connect-popup/src/connectPopupReducer.ts (2)</summary>

`4-4`: **State structure updated to support permission storage.**

The `ConnectPopupState` type and initial state have been correctly updated to include the `permissions` array for storing remembered app permissions.



Also applies to: 8-8, 17-17

---

`52-53`: **LGTM: Added selector for app permissions.**

The added selector function `selectConnectAppPermissions` is a clean and simple way to access the permissions array from the state.

</details>
<details>
<summary>packages/suite/src/support/messages.ts (1)</summary>

`3544-3547`: **New message definitions for connected apps feature look good.**

These message definitions properly support the new "Connected apps" section being implemented, providing localized strings for UI elements related to Trezor Connect and WalletConnect functionality. The additions follow the established pattern in the codebase for defining localized messages.



Also applies to: 9602-9605, 9643-9655, 9677-9688

</details>

</blockquote></details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->
state: COMMENTED
author_association: NONE
submitted_at: 2025-03-19T14:40:05Z
user: login: mroz22
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-03-19T14:44:22Z
user: login: coderabbitai[bot]
body: **Actionable comments posted: 0**

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

<details>
<summary>packages/suite/src/views/settings/SettingsConnectedApps/WalletConnectButton.tsx (3)</summary><blockquote>

`15-19`: **Consider adding loading state feedback during connection**

The connection process might take some time, but there's no loading indicator to inform the user that something is happening after they click connect.

```diff
 const handleConnect = () => {
+    const connectionInProgress = true; // Add loading state
     dispatch(walletConnectPairThunk({ uri: connectionUrl }));
     setConnectionUrl(''); // Clear input after attempt
     setModalOpened(false);
 };
```

Consider tracking a loading state and displaying a spinner or disabling the button during connection to improve user experience.

---

`45-51`: **Add validation for WalletConnect URI format**

The input field accepts any string, but WalletConnect URIs typically follow a specific format starting with "wc:".

```diff
 <Input
     value={connectionUrl}
     onChange={e => setConnectionUrl(e.target.value)}
+    error={connectionUrl && !connectionUrl.startsWith('wc:') ? 'Invalid WalletConnect URI format' : undefined}
     placeholder={translationString(
         'TR_WALLETCONNECT_ADD_CONNECTION_PLACEHOLDER',
     )}
 />
```

---

`1-66`: **Add accessibility attributes to improve inclusivity**

The component lacks important accessibility attributes which are essential for users relying on screen readers or keyboard navigation.

Consider adding:
1. `aria-label` to the Input field
2. `aria-describedby` for error messages
3. Keyboard handling (Enter key submission in the input field)

Example implementation for the Input component:
```diff
 <Input
     value={connectionUrl}
     onChange={e => setConnectionUrl(e.target.value)}
+    aria-label={translationString('TR_WALLETCONNECT_ADD_CONNECTION_PLACEHOLDER')}
+    onKeyDown={e => e.key === 'Enter' && connectionUrl && handleConnect()}
     placeholder={translationString(
         'TR_WALLETCONNECT_ADD_CONNECTION_PLACEHOLDER',
     )}
 />
```

</blockquote></details>
<details>
<summary>packages/suite/src/views/settings/SettingsConnectedApps/SettingsConnectedApps.tsx (4)</summary><blockquote>

`26-26`: **Fix typo in variable name `activeItemdId`**

There's a typo in the variable name - "d" is duplicated between "Item" and "Id".

```diff
-const [activeItemdId, setActiveItemId] = useState(tabs[0].id);
+const [activeItemId, setActiveItemId] = useState(tabs[0].id);
```

---

`40-40`: **Update reference to the activeItemId variable**

This needs to match the corrected variable name from the previous fix.

```diff
-<Tabs size="large" activeItemId={activeItemdId} hasBorder={false}>
+<Tabs size="large" activeItemId={activeItemId} hasBorder={false}>
```

---

`52-52`: **Update reference to the activeItemId variable**

This also needs to match the corrected variable name.

```diff
-{tabs.find(tab => tab.id === activeItemdId)?.component}
+{tabs.find(tab => tab.id === activeItemId)?.component}
```

---

`13-58`: **Consider persisting tab selection across user sessions**

Currently, the component always defaults to the first tab when mounted. For better user experience, consider persisting the user's last selected tab.

You could use local storage or the Redux store to remember the user's preference:

```diff
+import { useEffect } from 'react';
+import { analytics, EventType } from '@trezor/suite-analytics';

 export const SettingsConnectedApps = () => {
     const tabs = [
         // ... tabs definition
     ];
-    const [activeItemId, setActiveItemId] = useState(tabs[0].id);
+    const [activeItemId, setActiveItemId] = useState(() => {
+        const savedTab = localStorage.getItem('settingsConnectedAppsTab');
+        return savedTab && tabs.some(tab => tab.id === savedTab) ? savedTab : tabs[0].id;
+    });
+
+    useEffect(() => {
+        localStorage.setItem('settingsConnectedAppsTab', activeItemId);
+    }, [activeItemId]);
+
+    const handleTabChange = (tabId: string) => {
+        setActiveItemId(tabId);
+        analytics.report({
+            type: EventType.SettingsConnectedAppsTabChange,
+            payload: { tab: tabId },
+        });
+    }

     // ... in the JSX update the onClick handler
-    onClick={() => setActiveItemId(tab.id)}
+    onClick={() => handleTabChange(tab.id)}
```

</blockquote></details>
<details>
<summary>packages/suite/src/views/settings/SettingsConnectedApps/ConnectPermissions.tsx (4)</summary><blockquote>

`40-40`: **Use translation component for "Permissions:" text**

The "Permissions:" text is hardcoded instead of using the Translation component like other text in the file.

```diff
-<Text variant="tertiary">Permissions: {app.types.join(', ')}</Text>
+<Text variant="tertiary">
+    <Translation id="TR_PERMISSIONS" />: {app.types.join(', ')}
+</Text>
```

Make sure to add the "TR_PERMISSIONS" translation key to the messages file.

---

`12-20`: **Enhance empty state with guidance for users**

The empty state shows that there are no connected apps but doesn't guide users on how to connect one.

```diff
 if (apps.length === 0) {
     return (
-        <Row padding={spacings.xl} justifyContent="center">
-            <Text align="center" variant="tertiary">
-                <Translation id="TR_NO_CONNECTED_APPS" />
-            </Text>
+        <Row padding={spacings.xl} justifyContent="center" flexDirection="column" gap={spacings.md}>
+            <Text align="center" variant="tertiary">
+                <Translation id="TR_NO_CONNECTED_APPS" />
+            </Text>
+            <Text align="center" variant="secondary">
+                <Translation id="TR_CONNECT_APPS_INSTRUCTION" />
+            </Text>
         </Row>
     );
 }
```

You'll need to add the new translation key "TR_CONNECT_APPS_INSTRUCTION" with appropriate instructions for users.

---

`49-58`: **Add user feedback when forgetting app permissions**

Currently, there's no feedback when a user forgets an app's permissions. Consider showing a toast notification.

```diff
 onClick: () => {
     dispatch(
         connectPopupActions.forgetAppPermissions(app),
     );
+    dispatch(
+        notificationsActions.addToast({
+            type: 'success',
+            message: `Permissions for ${app.origin} were removed successfully.`,
+        }),
+    );
 },
```

Remember to import `notificationsActions` from the appropriate location.

---

`25-63`: **Improve display of permission types for better readability**

The current implementation displays permission types as a simple comma-separated list, which might not be very user-friendly for non-technical users.

Consider using a more descriptive format or even individual badges for each permission type:

```diff
-<Text variant="tertiary">Permissions: {app.types.join(', ')}</Text>
+<Text variant="tertiary"><Translation id="TR_PERMISSIONS" />:</Text>
+<Row gap={spacings.xs} flexWrap="wrap" marginTop={spacings.xs}>
+    {app.types.map(type => (
+        <Badge key={type} variant="secondary">
+            {mapPermissionTypeToReadableName(type)}
+        </Badge>
+    ))}
+</Row>
```

You would also need to implement a `mapPermissionTypeToReadableName` function that converts technical permission types to user-friendly names.

</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 fd7141a0d321881696ce8abaad601513dce1a9e4 and d491354eb6fa95bef9165c58f148af4079aa8acf.

</details>

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

* `packages/suite/src/components/settings/SettingsLayout.tsx` (2 hunks)
* `packages/suite/src/components/suite/StatusLight.tsx` (3 hunks)
* `packages/suite/src/support/messages.ts` (4 hunks)
* `packages/suite/src/views/settings/SettingsConnectedApps/ConnectPermissions.tsx` (1 hunks)
* `packages/suite/src/views/settings/SettingsConnectedApps/SettingsConnectedApps.tsx` (1 hunks)
* `packages/suite/src/views/settings/SettingsConnectedApps/WalletConnectButton.tsx` (1 hunks)
* `packages/suite/src/views/settings/SettingsConnectedApps/WalletConnectList.tsx` (1 hunks)
* `suite-common/connect-popup/src/connectPopupTypes.ts` (3 hunks)
* `suite-common/walletconnect/src/walletConnectThunks.ts` (1 hunks)
* `suite-common/walletconnect/src/walletConnectTypes.ts` (1 hunks)

</details>

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

* suite-common/walletconnect/src/walletConnectThunks.ts
* suite-common/walletconnect/src/walletConnectTypes.ts
* suite-common/connect-popup/src/connectPopupTypes.ts
* packages/suite/src/views/settings/SettingsConnectedApps/WalletConnectList.tsx
* packages/suite/src/components/suite/StatusLight.tsx
* packages/suite/src/components/settings/SettingsLayout.tsx

</details>

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

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

<details>
<summary>packages/suite/src/views/settings/SettingsConnectedApps/SettingsConnectedApps.tsx (4)</summary><blockquote>

<details>
<summary>packages/suite/src/views/settings/SettingsConnectedApps/ConnectPermissions.tsx (1) (1)</summary>

* `ConnectPermissions` (8-67)

</details>
<details>
<summary>packages/suite/src/views/settings/SettingsConnectedApps/WalletConnectList.tsx (1) (1)</summary>

* `WalletConnectList` (19-85)

</details>
<details>
<summary>packages/suite/src/components/settings/SettingsLayout.tsx (1) (1)</summary>

* `SettingsLayout` (80-95)

</details>
<details>
<summary>packages/suite/src/views/settings/SettingsConnectedApps/WalletConnectButton.tsx (1) (1)</summary>

* `WalletConnectButton` (9-66)

</details>

</blockquote></details>
<details>
<summary>packages/suite/src/views/settings/SettingsConnectedApps/WalletConnectButton.tsx (1)</summary><blockquote>

<details>
<summary>suite-common/walletconnect/src/walletConnectThunks.ts (1) (1)</summary>

* `walletConnectPairThunk` (353-369)

</details>

</blockquote></details>

</details>

</details>

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

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

</details>

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

<details>
<summary>packages/suite/src/support/messages.ts (8)</summary>

`3544-3547`: **LGTM! New message added for Connected apps section.**

The message key `TR_CONNECTED_APPS` has been appropriately added with a clear default message, which will be used for the new Connected apps section mentioned in the PR objectives.

---

`3602-3605`: **LGTM! Message added for the Connect modal's "remember" checkbox.**

This message will be used in the Connect popup modal to provide users with an option to remember app permissions, as mentioned in the PR summary.

---

`3643-3646`: **LGTM! New message for WalletConnect connection action.**

The message key `TR_WALLETCONNECT_ADD_CONNECTION` properly replaces the previous `TR_WALLETCONNECT_NEW_CONNECTION_URL` mentioned in the summary, with a clearer action-oriented label.

---

`3647-3651`: **LGTM! Descriptive message for WalletConnect connection.**

This message provides helpful context to users when manually adding a WalletConnect connection.

---

`3652-3655`: **LGTM! Placeholder message for WalletConnect input.**

This message will be used as placeholder text in the input field when adding a WalletConnect connection.

---

`3677-3680`: **LGTM! Empty state message added.**

This message will be shown when there are no connected apps, providing a clear empty state indication for users.

---

`3681-3684`: **LGTM! Feature name message added.**

Adding a dedicated message for the "WalletConnect" feature name allows for consistent labeling throughout the application.

---

`3685-3688`: **LGTM! Action button message added.**

The message key `TR_FORGET` provides a clear action label for removing connected apps or sessions.

</details>

</blockquote></details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->
state: COMMENTED
author_association: NONE
submitted_at: 2025-03-19T14:56:33Z

Review comments:
diff_hunk: @@ -0,0 +1,85 @@
+import { useState } from 'react';
+
+import styled from 'styled-components';
+
+import { selectSessions, walletConnectDisconnectThunk } from '@suite-common/walletconnect';
+import { Column, Dropdown, IconCircle, Row, Text } from '@trezor/components';
+import { spacings, spacingsPx } from '@trezor/theme';
+
+import { Translation } from 'src/components/suite';
+import { useDispatch, useSelector } from 'src/hooks/suite';
+
+const AppIconImage = styled.img`
+    width: ${spacingsPx.xxl};
+    height: ${spacingsPx.xxl};
+    border-radius: ${spacingsPx.md};
+    background: ${({ theme }) => theme.backgroundNeutralSubtleOnElevation1};
+`;
+
+export const WalletConnectList = () => {
+    const dispatch = useDispatch();
+    const sessions = useSelector(selectSessions);
+    // TODO: we need some sort of proxy to load images from 3rd parties securely
+    const [fallbackIcon, setFallbackIcon] = useState(true);
+
+    if (sessions.length === 0) {
+        return (
+            <Row padding={spacings.xl} justifyContent="center">
+                <Text align="center" variant="tertiary">
+                    <Translation id="TR_NO_CONNECTED_APPS" />
+                </Text>
+            </Row>
+        );
+    }
+
+    return (
+        <>
+            <Column flex="1" hasDivider>
+                {sessions.map(session => (
+                    <Row key={session.topic} gap={spacings.md} padding={spacings.md}>
+                        {fallbackIcon ? (
+                            <IconCircle
+                                name="appWindow"
+                                size={spacings.xxl}
+                                paddingType="small"
+                                variant="tertiary"
+                                hasBorder={false}
+                            />
+                        ) : (
+                            <AppIconImage
+                                src={session.peer.metadata.icons[0]}
+                                onError={() => setFallbackIcon(true)}
+                            />
+                        )}
path: packages/suite/src/views/settings/SettingsConnectedApps/WalletConnectList.tsx
user: login: coderabbitai[bot]
body: _🛠️ Refactor suggestion_

**Improve fallback icon implementation.**

The current implementation uses a single boolean state that affects all session icons if any one of them fails to load. This should be tracked per session.


```diff
-{fallbackIcon ? (
+{failedImages[session.topic] || !session.peer.metadata.icons?.[0] ? (
    <IconCircle
        name="appWindow"
        size={spacings.xxl}
        paddingType="small"
        variant="tertiary"
        hasBorder={false}
    />
) : (
    <AppIconImage
        src={session.peer.metadata.icons[0]}
-       onError={() => setFallbackIcon(true)}
+       onError={() => setFailedImages(prev => ({ ...prev, [session.topic]: true }))}
    />
)}
```


<!-- 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
                        {failedImages[session.topic] || !session.peer.metadata.icons?.[0] ? (
                            <IconCircle
                                name="appWindow"
                                size={spacings.xxl}
                                paddingType="small"
                                variant="tertiary"
                                hasBorder={false}
                            />
                        ) : (
                            <AppIconImage
                                src={session.peer.metadata.icons[0]}
                                onError={() => setFailedImages(prev => ({ ...prev, [session.topic]: true }))}
                            />
                        )}
`````

</details>

<!-- suggestion_end -->

<!-- This is an auto-generated comment by CodeRabbit -->

✅ Addressed in commit d491354
created_at: 2025-03-19T14:40:04Z
author_association: NONE
diff_hunk: @@ -0,0 +1,85 @@
+import { useState } from 'react';
+
+import styled from 'styled-components';
+
+import { selectSessions, walletConnectDisconnectThunk } from '@suite-common/walletconnect';
+import { Column, Dropdown, IconCircle, Row, Text } from '@trezor/components';
+import { spacings, spacingsPx } from '@trezor/theme';
+
+import { Translation } from 'src/components/suite';
+import { useDispatch, useSelector } from 'src/hooks/suite';
+
+const AppIconImage = styled.img`
+    width: ${spacingsPx.xxl};
+    height: ${spacingsPx.xxl};
+    border-radius: ${spacingsPx.md};
+    background: ${({ theme }) => theme.backgroundNeutralSubtleOnElevation1};
+`;
+
+export const WalletConnectList = () => {
+    const dispatch = useDispatch();
+    const sessions = useSelector(selectSessions);
+    // TODO: we need some sort of proxy to load images from 3rd parties securely
+    const [fallbackIcon, setFallbackIcon] = useState(true);
path: packages/suite/src/views/settings/SettingsConnectedApps/WalletConnectList.tsx
user: login: coderabbitai[bot]
body: _⚠️ Potential issue_

**Address the security TODO for loading external images.**

The TODO comment identifies an important security concern. Loading images directly from third parties can expose users to various risks including tracking, fingerprinting, and potentially even XSS if there are vulnerabilities in the image rendering.


Consider implementing one of these solutions:
1. Use a proxy service that sanitizes and caches external images
2. Use Content Security Policy headers to restrict image sources
3. Implement a local image caching mechanism

```diff
- // TODO: we need some sort of proxy to load images from 3rd parties securely
- const [fallbackIcon, setFallbackIcon] = useState(true);
+ // Track failed image loads per session
+ const [failedImages, setFailedImages] = useState<Record<string, boolean>>({});
```


> Committable suggestion skipped: line range outside the PR's diff.

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-03-19T14:40:04Z
author_association: NONE
diff_hunk: @@ -33,9 +35,19 @@ export const prepareConnectPopupReducer = createReducerWithExtraDeps(
                 if (state.activeCall?.state === 'request')
                     state.activeCall.confirmation.reject(payload);
                 state.activeCall = undefined;
+            })
+            .addCase(connectPopupActions.rememberAppPermissions, (state, { payload }) => {
+                state.permissions = state.permissions.filter(p => p.origin !== payload.origin);
+                state.permissions.push(payload);
+            })
+            .addCase(connectPopupActions.forgetAppPermissions, (state, { payload }) => {
+                state.permissions = state.permissions.filter(p => p.origin !== payload.origin);
             });
path: suite-common/connect-popup/src/connectPopupReducer.ts
user: login: coderabbitai[bot]
body: _🛠️ Refactor suggestion_

**Consider using composite key for permissions.**

The current implementation filters permissions by origin only. This could cause problems if multiple apps from the same origin but with different process names try to register permissions, as they would overwrite each other.


Consider using a composite key of origin + processName for filtering:

```diff
-            .addCase(connectPopupActions.rememberAppPermissions, (state, { payload }) => {
-                state.permissions = state.permissions.filter(p => p.origin !== payload.origin);
+            .addCase(connectPopupActions.rememberAppPermissions, (state, { payload }) => {
+                state.permissions = state.permissions.filter(
+                    p => !(p.origin === payload.origin && p.processName === payload.processName)
+                );
                 state.permissions.push(payload);
             })
-            .addCase(connectPopupActions.forgetAppPermissions, (state, { payload }) => {
-                state.permissions = state.permissions.filter(p => p.origin !== payload.origin);
+            .addCase(connectPopupActions.forgetAppPermissions, (state, { payload }) => {
+                state.permissions = state.permissions.filter(
+                    p => !(p.origin === payload.origin && p.processName === payload.processName)
+                );
             });
```


<!-- 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
            .addCase(connectPopupActions.rememberAppPermissions, (state, { payload }) => {
                state.permissions = state.permissions.filter(
                    p => !(p.origin === payload.origin && p.processName === payload.processName)
                );
                state.permissions.push(payload);
            })
            .addCase(connectPopupActions.forgetAppPermissions, (state, { payload }) => {
                state.permissions = state.permissions.filter(
                    p => !(p.origin === payload.origin && p.processName === payload.processName)
                );
            });
`````

</details>

<!-- suggestion_end -->

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-03-19T14:40:04Z
author_association: NONE

PR comments:
user: login: github-actions[bot]
created_at: 2025-02-28T15:15:32Z
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/481edfc9-88f7-40d7-9d04-f4cc5ae1c926)**

<a href="https://qr.expo.dev/eas-update?appScheme=trezor-suite-preview&projectId=15998f8a-e75c-4b60-959d-6f68e5ff4936&groupId=481edfc9-88f7-40d7-9d04-f4cc5ae1c926"><img src="https://qr.expo.dev/eas-update?appScheme=trezor-suite-preview&projectId=15998f8a-e75c-4b60-959d-6f68e5ff4936&groupId=481edfc9-88f7-40d7-9d04-f4cc5ae1c926" 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-19T14:40:01Z
body: <!-- This is an auto-generated comment: summarize by coderabbit.ai -->
<!-- This is an auto-generated comment: failure by coderabbit.ai -->

> [!CAUTION]
> ## Review failed
> 
> The head commit changed during the review from b7de58180b721e9e5e07d0d0038d2a467ea1adc4 to d491354eb6fa95bef9165c58f148af4079aa8acf.

<!-- end of auto-generated comment: failure by coderabbit.ai -->
<!-- walkthrough_start -->

## Walkthrough

The changes implement a new settings section for connected apps across the application. A new component, `SettingsConnectedApps`, is introduced in both the desktop and web routers, with the web version utilizing lazy loading. A new asynchronous action, `saveConnectSettings`, is added to persist connection permissions in the database, which is also updated with a new schema property for managing these permissions. The settings layout now includes a new navigation item for connected apps, with corresponding updates in mobile and sidebar navigation to recognize the new route. Additionally, a new variant, 'info', is added to the status light component. The connect popup modal is modified to include a checkbox for remembering app permissions. Middleware and Redux logic are updated to handle actions for remembering and forgetting permissions, with associated changes in reducers, thunks, and type definitions. Modifications also affect WalletConnect components and types, including session validation and navigation adjustments, and a new route is defined in the suite configuration.

<!-- walkthrough_end -->
<!-- internal state start -->


<!-- DwQgtGAEAqAWCWBnSTIEMB26CuAXA9mAOYCmGJATmriQCaQDG+Ats2bgFyQAOFk+AIwBWJBrngA3EsgEBPRvlqU0AgfFwA6NPEgQAfACgjoCEYDEZyAAUASpADK2VmgqyjcEj2wAbb5AokAI7Y0riQ4rjedJAARADC+BjkYpCIJLjiGETI8FiO6iQxkADuaMiI2ALM6jT0cuGwnthpfMwuuLIA1pjoGPSo8EoY4gBm8NH1uI2QGE4ClJAAjADsAMyLqxow07zwba6QI/gMzfwjDai2KMhTnrm4FIrYYvCJZ+gzJMWxCUmitehuNxEEU0i83sV1AgsLcYAEAF74Pj5GgAGhKCAYsGukCUiHgRHI9AIhzQDHg3nU1E8TD+4IwyEhUzhJERfF+yTCmHoAHU0L50hz/hp3NM8QwKPBuOI3rx8BJBtF8HhKeQbjtsBRuPg0u9YeRvmCZRh0SRmNxYGV4PDckQULhkI8oigsGRLRhyVkGk0WpASAAPbiUcYezz1fnefCQr3NSg3fCQNoYNCkb3wPi0znRNBAykMaivBmJpGeEgjEb/SQkbyyLYAQVotHUhYjsnRaFSEpIZEQsHwYQGHu82CUxITCoqrf8JF40nY3sY7tIyDaShdC5Rd2GlBGZJI6N7Ufz+K9+q+qUriQA5MhvGhZMquX1cdICRgRQYAGJIw6a258JRcG0bxEHRWFuB8PwAmCUJGB6eZ0AYBhpDSehqAXI5fCjW1IAAVRsAAZLgAG0hRSNIMltHI8mwAoAF0AApYAyYEOAAejYogoUqDQmGYNiHlZJEBIRJEwAqAo2Ig3w2JWdZVgASg/fRjHAKAyHofBzjQPBCFIcgqABPi2GGLheH4YRKykGR5CYJQqFUdQtB0FSTCgOABmQHodIIYgyGUIyWBMzh/DQQ0nH2eR6js5RHM0bRdDAQxVNMAxuDJbplzYiSaDAPFOgIbgwFo7KKAYbLsCBJFcDYmxH0oDQHX9DgDBiNqDAsSA6wAST8gzqXoCpnAOLTF0wZdRRpJdpBdB4nmQhc9m1CggONd4OwNBRzUSdh0QAA3sdJMmyMjajrIFED29ERkeZgF0QINyTGaJ0qmLYPOQPjtXIYYcVuLA0EbaISVhPavp24ZLoskQxHRYpMWxNpgVSB4cM6EhZHjNMMyRAIHsSJsvRsEgyTCcGfodd7pk29gDlyBcwZYb72ChwQYYHLzEEQY54AGkooQXdH5D2m8jqosBM3+OgwBzYErz2rYADkE37RpWkUeAxnzY0sYDJBjq25nIf4PhIy4hh0ACHg8fnenYSbcsP3MSw628GhDMLLH7dEO8PcSZBRoDZaAR/CCBDzP1hmbaQjCgRXz3J+dAdHLgDrFrJEFOuhzuBPaUG2lbohulhID2xAyrYhUvkQbL0+yNjDsojOs9oHOa8b47M8SLNW4uhWDCgBJtopxNZZwyraAG1PRab7IJe7qXaBli6ry4DuqJbtu8+T4GE1BxPIbztn/nXPb0oYTLpAqgo8ukAr8CKkry/KioqpW2r6ooRrEH9fu2piWOqVz6XxrjlEgYBigkAEKVF+lVg4fzwA1JqLV/4dRdr1fSAVohDUiu8LE40Y4GDrJ8b4tN5CWhkN2AGQMxwMwPg6POAQmAUH6DCaYe06qIK/k1POYwohU08DvegZCcRHD4OBFMnhkxsFLjPTu886S1GXnLK6GJ4BYkgHgCk1oZqgzvPCWQvDsAejWiSWgshpHqKnEtaqDN17NwXmIbOfdDYQzCMXO6sIHqiE1uMegr1YBU1QMnZsbwIxRmQGIhc+j5CRkBjhUaoN7EnUcWdFx9CDx7ApC4cIe9ph60QAbWm4xIk/lVgsCindXEU0QErBMBSinDElDNSBVsAjMHlEqdWDs/HrimKgB2IwnZoK6m7AKntckLiUAwX2BZ/bvCDtVLpXhw7qMjhEEpsdIDx1IU0+QQjp6VPFpLJxS9ZaIFXpAGJDEGIKUgAAXj0PnYODE2IACoShQOAXEWAxjOiKzQGwLgMQjkZyKG8tikAbwVyrsUUBdd24Is3hdBuSLUnOLlkpf6NyADeDg0WKIxcgAAvncx5kAGJ4qULuHwIVkld0Jb3ZGpKFIKTzvTRmw8WaMNEEiVhpdgGSNAbRXKkDoHPwqm/GqnD3bf1/q1dqA8gEZSFdfGgMC2Kk09mqkg2UCBUFIHWektSHQoPap1HqfUsGDQii4eQo18FZEIcQzaZQLFYkeBgZUkTjH0lLogNAUhTr0rzhQyA8wyDoBoZM0GhSkSSKNTrb+vCKQkCCT6kxhZFyiE6Dkc49tqAqDKHcLySEULwHDoI58ARUYkGsgzUoApcB50KdSQ4t0FzE1oNgf0KMSxbG6tpMtXMK1RHROoKNtAvJYAKJ4vJnhJ5AQEMWzRfQFiwivCc3AV44I0CIEiNsfbJSnnYUGCg1QR3+14R2vajaoiaC3VYB+lUNBnovfiK9Ai/T+n1jhEYvq1pl0DSQAAok0h+sgfk5s/MBHlbRcjIF9dNWgcMEboBAgmLEObSkAULcu3UZJkIjrUJSDovR6A1uafWvaSgFREZ5FCGDqbaBgbmtwSDjQL7XpLl41aYYywlknThCdINpiixOJKDoO7tYkH3XTNhC68PFo/B4ITa1RpAaDakkNs1HjdqI7+TNiR+TqHkCSANUgFCKKzaC7IB5Hq+PzL4MzOxKDvomQEO8AJRM0lSVm7UEFuCofUdiUoyAvVhFnAqb1NZIDuloFEflsIYrDItWMv2RYfMvhmS4OZRZA6BiWZpPgYcI7sGjogLZABZdIfY0I0NThZkgwaEXsqwGfFVWUwEaq1f7HVer42GuNcmhVAClVgCMIKrrIrdUSvofC2eiLFsEXvI+OVZqAEWowf5Qy2DbUjXOI6iaBhVODN0/NGaG1zzJgVEQPL9ozQxvYbZxAjgBDpWXNvCgVB5BMmhHYhFAAJEm9k870K/UIh7d0w09D2oMI+5w5HHPRWcle7ZS4RCiHnKY6EtGUnhLo9h0AqAMi84WMHTM3H82ZLCRUUdnp8BiNAGwAB9OIAB5RWisQNxGgCBgAIizusVgrD2BiOjs+OpQkYBbekSZV5diRSvO2Z8sOkCA8GEMPOcoz1kZxxzKzTZjRTgouG4tmkFOlyQPzqB2AiBVcUCQRNVYW18fIwuPGtK9TsOt3fQqtyjFGffF1Rs0vWzjoHEOEcl3S6LrQGAGghT4foAyJKAQiDDg/kTwbCCWodTSAPHLkkV4AACL22ImWwGxLd0tznK/dx2MGEZl1ccM36/XuIkCvSw15GYgaCT3d6zCBMSOM4KJ7soi56JZwtAVF6dKVA2Du1qZNb9v6vSzuogzF7b2PvSC+z96c8GsBIYISh93Z5vizpxAhygAJ5hiM8Bul7t909EB3bO9NY0nWR3dAZ/U/ed26m0oEygawEKgzods0wL2Vyq2eA9mPiWsU4USNeaEuYViOswWzox+QEuQOEsIDSf6AGLYpG9q+a0w5SfAN2A+a0m+H4aW7seWXsYoPsuWOsCyhWhcxWKyZWUc4ghCccCY/IjB7BWW0ysy7BBWwcyypWay5W/BjIlAngq4W4FwyAgyIoqC42k2nWV83Wc2lONS/W9gq0zQBEBIzE62o2IylqmCu2Nqw0ZB3+x2qmR2M09wemzwgiJCkAEgLgvMww6IV4uQRw9eWWB0phiA5hRAzEAAav4ZgM2uELIEGMFholFq8M0LFrkDMtHhbrFgrpKErkEaUBQBgLaPXtyFCniA8M8OIFIFeF/h0j0trFmkHNyOqJ4NqMRs6H4cODNFEqDAACR+GSiJHa6PC67yBQGeB7RVY5h1gUDZDY4pFprbCzFMCRgUDzHcBtYMxIzxFjHDDQD4AABCGURAjwxitACQWxgefqYaEaJ+3Ai6u8LouRa4HYSM3Af6We1M54wRGAoRvhCRgR6AXMPMBYG+YQf2C4LenQlxyoz4mxP4V48JiJ1xrsd+px/Rb2kQJA7OGAIGUQfhxoAADI0Q4Fkr7DWGBOwiidsTmHsaDKQLgN1H8BQOcRfBiX0LcUiPcWtI8VQpoi8XzCSDkcOGuBuiEfgDuqMQEbgGkdiMEhCeSHzLCclvgFsVCjQP6LgFiStDiSEJSR4LqG4cgG6JgAtJqVysMDeG3sbqQeGvslhNGHaCJgmAEDPknNQkbiQSjNSPQS7OlkwU9guqwRlgHOcIslwSbDwXIXwZsgPDAKsakG+NQJqE0KKbUKnB0EGA4JEdEXEaCWEPcpACBnqVQGIMALhN1IcQqUEYrnajugAD5QqlHlFZCtnVGhAUB1FVhXh6B7EdYXyqr6EVzzbGGFkWGaA8KQCABJhBjqmSYRmVETOfWYkQ8uWZWaTDWXWSWY2UUc2ZAG2VeB2RUSeT2YUn2S8A0ZeYCaEUOafFNnoTNhqpOd1iubgGYTOXKntNYdoWlLocKpJAYbaQ6P1neA+HgO3DNittBTVA7iRiQDVrMGxEhamqhdgM7v7FYagltlavYakPtk4eaavm4edvppdr6WHn4NeThCLC/qgZPvLGGZbIfqNFcYntmhfEXD+FqpZq2t+YyFCDMaXBhVEFhThQyBTuBU0RrEgepkhJqFvqDEgM7lIBMQ/N7rMRJU7vSN1DQMwNjpaGTLjNIN9FOmxTAZXhDqHmtO0X0J0YbtLohmunwPDCFvdAiomGQNgFDvzL4OGsoS4OjGhKWvUfuN6AEOGK6fgdMLLHmPdiSEwvgISDou7njJZdZW7okv8cUNkVHqOP4PVKlsGSIRMmIRGaGVIUVnGbIRbPIUmYIehhVfMlVTlpGRwdIdwQ1eshVp8lbCoZoYqhABNkBaOdNqBROYYSzJBXARBZuPBY+A3IqMuhQGxACrdnlnheaugoRXzDgnangtNJVkQj4VxVFYxQiuPovCxaokKZGpDuEfYCBtANAN1IrAAOL2As42Ds64R872AH73h9LsJbU0GFh/mHCppf4hIOWBgdHRL6zrR+HZKVoXiLYlWIKgRqLpEBDRZZHTFFXRDXWLZgC5BKD+jyz7Sj5zy0bqIkA02yJMX4AIbM1VFk3yJKBv7yxfoUW0D4AzQRboDlgnyYDyCEFeiXUBziIUFSBUBBXXl1GZk6Wlw3aGVmhQwuA/bojp4Dhciuk4aLRR4fpYCjRXaGg+UPy0GW7UFAFZrg5GDOyjJtWZbzrZYSETK1WxmhyVC8EbICGQC/Ctq/QTxTylyvXvWfU/V/UA1A1cAyokDERXjSJM10TER0TDkvkgXqpgVGwQXdZQWPiwUFDLV4CrU80uCbWAE7VzmLkSnR7IBc3I6Mr3Vg2CLfb3gjVjZjU6GTWvnTXlQflvnNH8g1xdo9oO6TzeBsS4QtC/C6m4DT38hsSnRPqBYr3eC7Wbb7V2GHUkUnUEJnWuGnWUVeEfCbRCWeDykQGzFIDExsDMDzABC0DY4e17Tr3PrcBb2yUF1Kk4hJiSL0DNDxWzGxhfkkChr4D4CdBf630Y0PAZSMiND/gLixgeWJFYztJmgv2AjcA8BuZIBm21LrECrf1xARhHyWQpCoDh3eYJiN1rhnxEOXoYDQCrGXQAMuA+jRBiV7SJC/BjDnoClQ1kNmxrIcqCOJDCPGWAMKW9Iki0h+nGbOad4PTUAaLeTt6elmi4MLCyyEPnrEMTKaxW6ICP36Ov2qJnyPBEaIAApsCqKc1IgEi5BfaCJBXenDBbCr5j3eD2nK1iCq3BLpZvFMPeGf2cadACD4C/zVLzgd7hJwqaItBexpVEDOhqUWN6PP1KFv0Blojhp4BqE8UxNxM4iJCxZNgPRQV8PnC2PHAoSONQM4gi1BDYD8jy58hNqnQmnTDF0lN5XKGKBdO0g0C/ShPcxRpCDND36/ZUBAg4TIQrTaCuhRDBRb6N63FOAy4JO/TinmiPCWZIh3blHwiD7PgaOehEBBmu3jLtUe3iFsHe3RmcEhwlb+0JmB1nVQAbkY0HKly0iFKQDEQP25Mv10CF5sk5NP2Qu0BZ2nxf2b2jPeB/1U4co539b51uK526r+MT10BT2otz0L2JBL1b1r2pIb2VRb1/nJk1ZTCKBpmEgZlWz0N0CpzSNAnphyMcrIu0uovosjyYvAXYszXgV4sV6ouEvdr+iUvz2UCL0BjL0ksCs/2ovQ0N0k23hpVrJRI4N5PHp2iGNvomO4XJn/POgcu0CpzAthB4pL51bQDqBjpWayMrbzDeDT52PNOApRWuNcQmhGPuaJAcNBgkrDnqu/37OR4CpivjnD2zWQz9YEu1REvyskuKsUDKt6mUvRuatzkMSQ4sPGNsPhv75KQAV90TUgLivlTVCNhRClFXx3rpADYGooWa7Ns8PfwbY2Hbb9QAhHUHbOGEKn3H3n0LSW0uWOnhqRgXzU7/axr6qSJVbdtfA8OiNvAd7xZRARUebVhikJiMyKI0vcDSWXRbD2AOZIHOYR5lN5pTJd6aONBoR+qoAkBQgLB7SGsv05xWCsMkNHx8B7RiKskAdAeex5ygMnr32IB1gYCyDs4jAwfiCkEDrRnft8DDMEbGqAOvtQsLiNsJabtWxOYgT+qrtDZJpNYtaLYB5w32VtEYB/6E7KEbstv2n/pB4mZkb1A9iajxXoRbo2Y+U8OpDAb0Dwz+QK0LiJUYH+ZQfzISd/v5Nxngf9gTN0lKH1D3CyYexwdxbciqh2gOqpI8Df2ITsH3Ae1S12gkc9tWwSMMBlX3NdUdVe3zI+0fPxmNWJlB3dQFxcgNZW7BeQB4qPrf2XuQDErtolyl5gLzysCJDV6pJgABaVRXgADc2dCbb5EqjnZHrbEY7bcanb67TbxXI2WhNbWLibHbkibE1KeBSapq1hBF+9w7h9Dqp15FZ9HhF2l954Oud+wsW679diM2/Opx9gWGbQbW7su4yEX+o3K00x5QtRwTr94JvQ0MJ84zazOEjeQsecGp0wfRIQsiW6rFVRjel3rTHeAwDo1Y2k5tNDmgZDe0D31D7M7xkpMepbobMlMN1Y5+nlGiwSAMXdThe0OcljRrdAgHZbZtKxEbTHKjroiNTl9083HYJIhGQUozNAVmnIhYYAnmfMZrbDuNlpHoYDuIhaxYSgATn0OYKg2ieuCYQDqYGDIb5rRYf2YlCnrRiQbnrsbtzB4ZnVNVbzPV9VXz/nPzWyOy1sD8Y3k64T24FAy3sxm4M3c3jQC3z5+XQ9jXpAzXZYrXnsyaLUkAugQLqSXAeKQsXAm6qSOXIJ/Rzv/PNPXA8PEL+TyPwPmd2XsXYfxK/5tXyqA9UrEqr88CbAXMQqfbHXe9O2B9jhR9TqJ9Yoms5wg3VF5Qdaygfgm0SfAaqYLX5RNnlue0FfKfDCMNbrdPNz0SxwJmFza0gx0wxOQkyIM2+DSVxoX6mEkYbpvlyfy4xnlmTxmvtrRgiwWwe0zObOnO3OvOAuQuIuwNXAH0k/lfdwQwowJSpPi8Q/in8y534ZNKbsB/kiPwKO+DIIIoAATMv6vxzlzjztACzlVuzvzjrAEQ/qIGKrKANOIgYbAe0PfggBXAoQH+coc0A6FSZxgl2sQV2KUExjoZx+meOWsEiBBFAxKonN4P4xFCbBS4q/HkEAIIhvUv+G/X/nWH5yC46BP/bqJzmgHbBUADfVMJTQwLsdrOWaEkDEAbD0BiBGAGICKAAAsH/VnFQIIg0DoALA3nELiYFr9v+vONgYrBZz84QM9gOIBwP37cCuixzRUL3nFCSgQCbwKJCEi9AdgxBB4VaDhBiAABNZUHBCwBJhOmajZOGfweJKE0BHYeivPhMHFV6gPTe9KdA0AAIAArDIJZxyCFBSghgaoKSGaCWcVgAiHWDiAgZAc7OAiDoKgEwCuB8A1MLPkuy95uAd4ZCH2G8D2Q0BsIJerEHCGCgLOQQogBIIMAAA2OIYrHZxqD6BW/YXKLgMGwD7+PAvoHwPVDoR/wndTwDEGVi+CAQ5yDocsDiEJDaB6/H/iMOKFT8j+5WZ6M5WaG4BTohwEmN+StjX8Xwt/MIEYKaGldjhqSDoQAA44hn4dnDYC+pvVthcA3YWryQHOUh8kyGIN+CWLpAOhRCZjqo1pLPtywR+TpM5QyLeoxhsxSgdQI2HqDf+3OHkP0NYGc4Wc+EAiKogh6hYyg04SoXuDqAuYpE54FfrILRGKDNhygxgcwMZEfV2BmVI9gCCMGkMJ2OfX/FaSfzTA+e+nHXnuGdJq8FQhMO0DMhJgBA+ACGLbuwSqI21D2XmN4qgQv6i8iwVRI4ScJ470g+OtYF2hLwebu0pk1VSQnLzqp+1VkSvCrCr3PC3DAWtInEZv0FxDDgaaA6lDpDv63CrwLcZ/leGTKq8nRoXF0UkL/4ACgBIAsAVVggFQCvRZYH0TcJKGeArwGA+8F5FdK4DSmssIMXHEdGpj5+qcVEfIPRH0CVBLIjEZoLO4CxvRXuP0SIMWGFh8x2yQsb8OdGljEhrIysa6LZFaCdBeg2scyHrG+iixV4Vwf5XzAeDMAXg2LD4LEFxZ/BsJQIajGCHyhFQlIyALqNSQaBWxIYosZ2LpFliGRGI3sakLxEZCshOQvIQUOHHYhRxKY34VeB3GKIUYxrfce2MP7FiKBrOXoX2MGE797xVw5MciKhQLCNRdeYMV+If5Hj4h9IpIcBMfFgSXx9wvptBO+ChiU4v4lnG8I+FfDEx1wlCSCNZKtiasvw9pJ0ltY4T1hp4isViL7FpCCRSEpMQ2PHGq9FxBIoMdH3Gr1cCuMKcYHClriLZUUi2ZFMCCpZntlODIHegOwOrdcs+vXY+kYBdTnhiYpMB0v6XoR942A9AT+tS2klQxHqM6cDFRSSzTA+EsxdeoZL/Jf5tJeOHRMgD7CwNIkHaWEJPV7Tlcn83PTALBPQJajfeJDAdGEEozjB60mpRlPg0CkTJYOdoWNNWH+CnRIOKPaDheCiBiB+Kz4Gpt3kaAHt5ksUx3me2i7GoFYZDbScIxBZYYL4T7GYZbCrS2Qn+5yMPmYy9TkA6pmea4g+2ylQVe8tw3ga0RPTTC1YswmYJhkakXRMO0VEaZBIugPsAg7lXvJSBBa4dBEs09RuSNkBgN0wJsNxgDGfByh7GOkqKvyESB2hr+206nkFPLJkhsQhjERBEzMGTFBaxQDwX5QaDoRkmiGNJpMnA5y5TWhksCJKCICkBjWHwbKa+wEE7sEwe7QUagH1GOlTMX6QZgbiCbnDogBU+bPF08S99RIAEV8ISFSCYwjK46IqgxTqjFAbGOzZgDLn2jnFaApAZxs+D2j84npUYGmZHAqCgyOwWVf2FWHdwTg5x9qJYpgB0T+IbYwwHasaJDKiEnmFo15t+nl42iA69ojCbGx/H6SpJKUq9Cb1j71tK4gkhbJ3FEmdxxJNcayVrJkk8Jq2MfOtg11hSGyqIxsjeCjjbhOyHEjKNuLJM64Z8FJuCJSTnxUk+F1JKQeGVpOTZhA06ek+lKbNDSki5+hfLwrQDsnhzR4yYafrxjbTDNGAmoeaZEH2S3kn8KgVJgzz2gQM+M0DWBsFKuG5BAeS6bWjDysy4FOycU4oHvCLnHwxAiALgDtBzFM5cZwdR4eCW5hqkAQlwjWZyGD4C8oY2ku7hFjVi9zXxnIIoKqDCpsU9oi8/4OYUKTCt2A/NFOfNPsiLSFqxcr0GnGWwLUiRaGB6R8EaCAwFgK48IK6yrRoRS4G8sQKcTwAEAZcWwU4tWCjALhb59kCXJQxYQ7zfo3UzMQuDrkX5pg4zecFnIky5z5xBc8ICoC/Qr8VA08/eWWHSnIDYQAlQuQIDNyoR+Alua+u2Dioxhvp5mJkBonmC4BIEkaaBdFDzCdAEkCmZgAAw5bOU9oBCzWswG6gFNr6e87cL9CzkELUFRC1AMo1oqxYD56nfDEqEtyIL5wwiqWZLzYrPMuqPnGQor36oKEHR3wGeWGOjkuyXEorXWXbINnCSjZpij2SijsU9xPZVsnif3Vtn8Tyo9smxY7McWLxXZb83AB/IyBi9kEafLqIO2tTEVFJh2PrudU2jByzK4FI6XpICVBKv5scyhJGgTnIQk5nAz6CnJ566JW5PAcYAZlGjX07WfmRILhAoBosAGNQqyrCAJE5i22ZlazIkBVx6T/G7OIMESEvleVxmToZyhOArSc8nCXxTVmVJTmxgeFsYfnC+1wBYgK5nQSZODMWW3TS4rS9etoAoBwA/k28P1DJ0tx89qARlaUFjC3Rfp/G/3Junt1yAQR3E4wWoTmPYDBgjOnEwiO7jlCSiZo+tL+cbVE5egfw045CKZ3k70gtgPIRoMoos5/K3g0i1hURwilk83gzSgZAsqwzn5acGAB5aD2eUIrZRRHKorCGuUIr88uS1TNcunHBV+AvSiYLZFYXHdimwSthOhAojDKkAoyp0tfRjR9k1irhFOePUYYegkQy0akDcBJyIAyc8yKJPKSRG1k/QGzFmHcxNEedZZMvS0QrOtGfNbR+i5qm2KMWCqwxqSz+YkDy6WKPF+s6uN4ozhuyUk9iiSSapZX0tXFtbMcpaq8Xl5fFpyfxWhNSRbzZyP8ftt7KHZ7ZolY7M6qpO+AJK1Zkc1+X6sUQBqMlwVLJaZMTkd1m+sxAJQGtsl5K1ZDkgnE5Jga5osZkAGNVUQ8mTJClhTZ+etIhlD5SGhlZ5NVHCyiB4BBwZoj4GoqiDw5uNJhjhEKSyBEsyXAusbUHVgqqiffNkLG2NoKqEpmzPeUktCkl9GQ9w5sW8DSA08T5cU57AlLECHQaesuXBZlPoAQKsBXxIsUctGkXgt1EnMAujX4RGAoVkaTdSQ3aneNFS9oUeMjE6Ry1PAr6rNHHkmTyK+AHYJaWEHM6RSReTBLYCBhumaj7sZ66dCgFpAANHxWMDsOSOqFak6hZjfBcEy6bqIrBwELGHEnPxVFr5+C/yXlntJp13cBI+sLiFZkvTfKswHEN8s3EvL4NAGjpdgOwhULUBZiJAFulLVUbKhl/LAPUDWVYgS5Wy1JPMsQBbo9lGAToAcrWiXCeNGAe0oVHUSLqC6foc0JGCwGUBHgfAPdr8XlFtBUwZG47s+BmUowh10QTGSgV5mFJVFHQUzipnlql8/AgyrUocBwHXyqpbCr0D32MFzhQw7wTTbjRA3HcgQkxMYiTznVKrjYiii3N5TYaM8gInS4zn0DBXCjteDahoFcRiJTJmNr02YCqulmVV1VXnfLFaN9o6rlZBi1WcYuwnryE1nIJNTrPcVm9PVCKO1QyicUops1+sF1aNRtnuq+t1ir1Qihtxv5Bt82u3F7PT6hqHCfsmJcpJOx59YRh4FJrCEol9E1aHW3pqkjAXuI3J7CelEtqIDnaM1Z8zuDduhqWTr2t7KxPe2Rpqh3pZMAIHzEbz0pDo9IbHE/PoAxAAlRQQ7ngSM4naIhZ2tWZaFn7CkWe6QOgHZOmiKqskyYbivbC7xQU1aASzGlUiNBZoPE5ot/ITqoiwEEK7YMWi8CM4FaluYo+oF6iuSnSFgHGqUYhEOkkgAlFPTkUXGIKqMBqQvMhQinF7VbHm5ojVfLJjK+c+qTVIOkPH02HbOWpcYAADsvAwgn59yMHZ1v+AxAnkwAAnWxEN2DbAdxoJ8hymu225btasixb1rzoCTrVs2xbDdsW027xtvdSbVNUd2eKZtA263QtoCUrbwl8ksNRtojWr5kdAISyfGtO2KJoakOokB8BjWhzVGsa/1ikr11iB+lGiZbpzzFKwKqle0+gIUuCjvBWlN6t9QqA7DB6plSSgtdEGcklqKgWjZumXOpA2M5lGK2AIzL0kQM91+qSbtWp5VVFpNt04bJACbU4E1m2YVnU5hrU5jkVmuvCJ8qqLLqpA77SKlXomSk73JGbI9GmnIopyOdc+hnTuDFFPcSaaEEybiuejPKokryrmeuqwDNK7uzKr+ZMmt5tpl9fqA6ShC2C4RvojARlUZzhXBtQYMM06NuywDj7gY7COTWex2XKbVNkMrAJcKf3RA39yJKIDw2JDTB7lJTe/RSqL1JKhV60upi/PA3rQUF0W9ELFq9AkwNEmm+0kvkBiFoSZHxBnkGEoC0b/W6IAkTls025IfirnSACCL9Dcby0vGjsDUxE3gGSgpI0/eDzQxwH8DsxRA5yAU1KbfkKmtTYIL3hEMsdgo/9dIfQPQqQD6iMKh+HZwK0IwOnNWYUpAa+gRRRWlzdZlPBCEspwmvzKFo7SV66DuWhLAOrdzcKOR7lBngqsJ4sJbQNYcXRos84vNvODWuXXooV2/MB5S6s0FRNTiy6rMILAJeaod2zYndQkl3Y9pt1sRg9LiibeNSS7GRUuW6DLt/XfLUtipbXLuWEtsI+zw9x1f2S4R20F801/+Ypa6j9RdhqASIXGr+0D4UBkpwPbWkzN+n6kgQk8w9WBA/pRdAsl7X7sKEgCnFVYaBxDOhwyr7xftNAaSjAdLWVqTiWpNhZ+vOl8AsqVkQRO3k4bWwre/oelaXDLwzZkuHSDAGl0UTNHAspU1TDMbhaUB5jU8/QzuzQDowKh94MjXqFTJw8gQCPeFmsdR5YETDpcZY9CcPVoH0M0zQjDOGQGYakT+AQGGrQDQyI8yaxEQbIsPS05wu19cvUNO8kEMs5se4ycz18Ra8PisxAPpCdfpYnycpa09hPO/oVsuGLoJsANJ3VDTUA9Jtpv/Mb3SdRKluF7WQ3s5HGKUcOGvrzBoCUNfANjWWMc2axUN9oYwcor2BNN1L3cMx9mPab2h3J2kazVdG4STnO0bCEus0d7Gl3JGtVjWvznqqDqq9ARzo+1kfisYEm31ZZCYxcfpAMQNAqZtlKfAMJFTtjw2BhKrMjNhjoz+J1Y4ZK3KJn9KxoFM2meHKZmpT2Zjo/+SgBAHXiwid5pUqzOVQdjo0q/Dq1Lhqc5jxZi2Ysb0lFnuAYp7WRyhrP/Bz2l7GrrUaMD1GiegJpo5l24CtH2z3ASeis1T74VVtkSkdqRViW8jp+2S7wptDW4dB9ol06DuiESSonhTVjJHtJMzqTdQY6rSBmjyiqt8GePKsvfODC2LDLOgWU4Wy35UEHDTXTHlXQ2zICmAee3M0NKH2QNyokQPGE+9FGMJxi0XkK2M9Q9qv0vCFAVOH2bjOpTOaI5sczJV8YGB2SDMIiwOYWNg5i0DhvCys2b7L5+AJTCWmviTzz4SzGm/1jtKDa7dsVfEHCOlCHVUmb96h30s5TPPSS15V57WTrW7pfcyLhkhi7qHxDVAaS8gPhGxcfDRT5kaW0hQuEDb0w99OwSk4DFNC07Iqci7I3Ph3X/q3tL0OS+Zf/V8ZKLjJx0tCJnZpBj1FAfaH5cSmpJiLV6cdNRDmhmTgN6QKjLiZQuEm3LNa9EF9BS0GwCCP6bi3aCCsZS+AyFghS6fiOmipentJI/VuDOpHdV6RrZBWxFLNncj7zZIvmTfPlyMzM1dc5uaQRQxFye0PIyqeaud6Ah0ac88LAUsMh/e6J2Y4+Ytmh9qzbV2s5VA6vcJLoyZdks2AguhHoL1E09pVLaOBZVr4gfkJAy4D9WaAs1pNu1aJZbmm+3V6M1scqj7XeY3gI6wPPXPvnBrxVYawKkMkkQ6IuXVq+dfmsbnLrnVhs/in8vqy8j0Z7Kw8MUShWQeE5ua1Oe/qLXk0aA/EKyzRn+pB9SUui1PIYgVKXrgNyBnVH7CQN0zCNgG0jcCwo3czrqhcylyXPpcVza5wGygZNSdGdzoerrr0dHZkVttU0SdieeG7fBRrEsaJjhDYCOokAd0EXXQl2uVR7TKB9kgZBgNIy9WFsL1EYuiY1SEq1GtaNBBCCZWFwi41AH2e+Owg+eD+QTP/roA4RLhtIWRvdn/1cxXtiBd7dCM1JrgN9/As22gXE1aivpDFaGzjdHOGSGIrJSBrcnTNVFgtT7Ti8MzuhtB1lxtnOfOAEZAz3Gjp52w439alSm15BhWvyamGG0oIHTdMC5cHNL7pgTrZlhJwibmTPAvt/Btps4YfhB08nIEKqZCmTX6ADEPaCAHBYim6Arphww7d5ZO3fWXMG/OBdqAAMgt5xpldShM1OaZG499TB93dzj7bNRJkkAuxWXew6MoF146YmFVrWSeyWCMF2ZuV4gvrFsmU7nuVLIBMuaojUzTmmAG3y7/iEs6Tvr61ZFA7JI4K+kstJyP7optS5NLE04hfbo91e+egntNMp7qARAGwqBBErnwsINPVgBtuNKEw0N80YffdwBhRAGeElX/dEERgvN4WwmjkEM16MJZa0JsEwjzkugz78B3zDyzgdrRs7S7fS8Fos0xnEeX9wcwAyYA+B6AuB2hMYmSDtqGpHDpO7vv4oXRvt6DX0DDm8C/bzEOAJlpKAJzennaDBIq5orllBnZduiyqwFwyOq8bEK0dWYyzqzkXuMd0PaCXkEhsggTnIVowEDYh1gBA15UmHY8UDVm/jDRxm8CeZuTmxA57NmyNgLFX5wuzo4OyFdxsJWb0Ggdx1TYWvA2KAQTm+CE/SdiAQTlUFmxk+4DRPabc5gwPTYBP5PcAhT1cxE9wBRPW77XTm90bW1RKI9fN07PnynYx5No1jsIGyb/M/g5iZDhx6JvYQuPcZNTzx7qh8d+OxAATgpp2v4RT6bhCjfgePJKcunGrYYfZKHlsE+Fhrl56SffbnvIalLsPJZw4/rCQjkwH2mdvSf2j3nBHDj8K9XKJBz2ez550/q3tunN1TL7MxpvYxaZ96lyEbS+f4L2j0moYUzLw+xVBoaZrn0ksE7lIFt8jW+uJ1GarSzkwuUBDdgCyuc0lC7+OxNZhGKpzxyXfzPjdRYY8SPaKUjZj5rfqqsCTENezo0azKa4DIvpriLO2KifVY7OJzwTxczU7qfFPIn0p5p8tdie7OIbDVlUy88xMou0BPzmaOnd2lcA2hNjbOy0y1drjbtOW6F5w25djOnzfL9rFU8aNM2WjDTpp+j3Kde66jIrhmzqgURjAiAGqGWtub2pc2ej62vo5toDlxLzwl1Yzpkt9JvFQYMtEGsTW3wuvqnC5nlp64lTeum+2p1TJDjDdhoMHWpcfqJbZcrQSkXAZMntDTocC6aiAW6qchYqlvXo7sGXG7xtX1xmKUEqALwqBAVuXsQY7bcEjue9Pe8NMPoNqHuBV3TDWNaDcaAoX5vQtP4e2slQ9ozTkYxOiEJqe8pY0RRuvOpFxYNgy1BqIzHpMshV3mTpF00Qq2qql11aoy5Vpl98xVmyuw3gLAwPb3bdp03erNNunXmnynLKAGAJt+Xlbdo58G77hFKxQ5QxuEXcbsuAm9S5JuPXXr+qOzaj4VOrXgJ1pVug1SaH/gZTjm767ad7meuQbgY+i/GGRWvCFzu5102Oelw+igwPLKoiywdywg8wHCGofu2abWXD8HUPyBzgWmUDqthwG9oo7MmBmNcmjyZkXSFgzIGkW0Jx+6L8gNAtHyT2avDcprI3tCCROJepNZzb02e/UsNiawHrUeaB+Si0UHxG0GYSn+j1lo7CJWWGEwrIHJ+49otJkCEeuxmuENNY0DADNoPshstxZ/5whqz7QSckmdogz0hkIJEBSUPpwy08gp4Ewf2kXOR+NZm5S9OcHJSOEEzT+HM22D0HFknAQVmIcj8yG8NQDULXCz9hWqBjJDju+WYExXKcZPzX4BGDj8ZAJTTF85SA0sf1x9jb4/2uh3BfxTw1qrQkdq2lXr3pj3qmkYscq9MMZ9el7LxvfTfzHPzfd6PDXBQF0Vjsa2c69yeiv0PqSTD3p5lM+vd6fr9p/uez7HYo1tKudp9e+4SfrPvJnCwzACVGfxTm7vcKt0LdkZgkq6QQamQ7zUqgI6MUhZ4DyoBB3LoMh7l3NkSxEgB3UfnMzRFifUEfBEJH8zRGdXhcIisAANK9CeQisPmqV/7d5VUAn1z6W9708feVPX360sKsFMfAhvG6vjKaFY5WlhMyAoDdi4CBfpdTar696DHe9mHFuF+haO6fpin4nU5G/L9SO+CPfKQynvZp9Yp9S5jQEwdIIwrr46aGA28JmelHTC2gTiohrSrrhKSje6X43hl8t4V6rf731gX7/s/a0s+MAAAfjd7o/Mf95NH4j+R/3lcfBP9nET5y57F6fWamn6L4zMwe0P9wjDxKiw9iBTvjrwBMlDciRxNI2kXSGHp7WsB2AXAKgOFCz7RRHcDkNQPFBchJQDA6f4yOoBZyDBEALOAmoJLoAs5W0NjlSNX7UiHBaAywRYJIMWBoAyStAVYK/0WBPCnhiwToQAE5OhyEJ4UWkBidCySiwaIRsFoDIRB/0/kgJIMSiGB0/tASQdP42DRDJBUCTobuGn/RCH8x/zodEIYDRCnhIwfvwv5GCSCySywaf2gDQAL+GA5wTvzX5BQdfg35N+daC360ALOBpB7+QAA -->

<!-- internal state 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=17313):

- 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 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: bosomt
created_at: 2025-03-20T09:27:38Z
body: **QA OK**
- hidden when debug mode is disabled
- 
<img width="721" alt="image" src="https://github.com/user-attachments/assets/4d313ead-9bf6-4d93-91db-e09253655f6d" />

**Info:**
 - Suite version: desktop 25.4.0 (e5ccc0c45bf1c935d6c0c030e02452d2cacf234a)
 - Browser: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) TrezorSuiteDev/25.4.0 Chrome/134.0.6998.44 Electron/35.0.1 Safari/537.36
 - OS: MacIntel
 - Screen: 1470x956
 - Device: Trezor T3B1 2.8.9 regular (revision fad9682201cf9289bba2adb66e6e07ed1cf78936)
 - Transport: BridgeTransport 2.0.33
author_association: CONTRIBUTOR