main d3974b085799 cached
917 files
4.1 MB
1.1M tokens
790 symbols
1 requests
Download .txt
Showing preview only (4,514K chars total). Download the full file or copy to clipboard to get everything.
Repository: tetherto/pearpass-app-desktop
Branch: main
Commit: d3974b085799
Files: 917
Total size: 4.1 MB

Directory structure:
gitextract_j12gn_f4/

├── .claude/
│   └── skills/
│       └── use-ui-kit/
│           └── SKILL.md
├── .cursor/
│   └── rules/
│       └── use-ui-kit.mdc
├── .github/
│   ├── actions/
│   │   └── authorize-pr/
│   │       └── action.yml
│   ├── pull_request_template.md
│   └── workflows/
│       ├── ci-pr.yml
│       ├── ci-push.yml
│       └── ci-reusable.yml
├── .gitignore
├── .husky/
│   └── pre-commit
├── .npmrc
├── .nvmrc
├── AGENTS.md
├── CLAUDE.md
├── CONTRIBUTING.md
├── LICENSE.md
├── NOTICE.md
├── README.md
├── SECURITY.md
├── app.electron.tsx
├── appling/
│   ├── README.md
│   ├── app.cjs
│   ├── app.dev.cjs
│   ├── app.staging.cjs
│   ├── entitlements.plist
│   ├── lib/
│   │   ├── install.cjs
│   │   ├── preflight.cjs
│   │   ├── progress.cjs
│   │   ├── utils.cjs
│   │   ├── view.html.cjs
│   │   └── worker.cjs
│   └── package.json
├── assets/
│   ├── animations/
│   │   ├── category.riv
│   │   ├── form_credit_card.riv
│   │   ├── pearpass_password.riv
│   │   └── sync_without_the_cloud_animation.riv
│   ├── fonts/
│   │   └── humbleNostalgia/
│   │       └── HumbleNostalgia.otf
│   ├── rive/
│   │   └── rive_webgl2.wasm
│   └── video/
│       ├── onboarding_lock_loop.webm
│       └── onboarding_lock_start.webm
├── babel.config.cjs
├── babel.strict-dom.cjs
├── build-assets/
│   └── win/
│       └── AppxManifest.xml
├── docs/
│   └── Electron-Packaging-And-Runtime.md
├── e2e/
│   ├── .gitignore
│   ├── components/
│   │   ├── CreateOrEditPage.js
│   │   ├── DetailsPage.js
│   │   ├── LoginPage.js
│   │   ├── MainPage.js
│   │   ├── SettingsPage.js
│   │   ├── SideMenuPage.js
│   │   ├── Utilities.js
│   │   └── index.js
│   ├── fixtures/
│   │   ├── app.runner.js
│   │   └── test-data.js
│   ├── package.json
│   ├── playwright.config.js
│   ├── scripts/
│   │   └── explore.js
│   └── specs/
│       ├── 01-Login/
│       │   ├── creatingLoginItem.test.js
│       │   ├── creatingLoginItemPassword.test.js
│       │   └── editingDeletingLoginItem.test.js
│       ├── 02-CreditCard/
│       │   ├── creatingCreditCardItem.test.js
│       │   └── editingDeleteCreditCardItem.test.js
│       ├── 03-WiFi/
│       │   ├── creatingWiFiItem.test.js
│       │   └── editingDeletingWiFiItem.test.js
│       ├── 04-Identity/
│       │   ├── creatingIdentityItem.test.js
│       │   └── editingDeletingIdentityItem.test.js
│       ├── 05-PassPhrase/
│       │   ├── creatingPassPhraseItem.test.js
│       │   └── editingDeletingPassPhraseItem.test.js
│       ├── 06-Note/
│       │   ├── creatingNoteItem.test.js
│       │   └── editingDeleteNoteItem.test.js
│       ├── 07-CustomField/
│       │   ├── creatingCustomFieldItem.test.js
│       │   └── editingDeleteCustomFieldItem.test.js
│       ├── 08-MultipleSelection/
│       │   └── multipleSelection.test.js
│       ├── 09-SortingAndFiltering/
│       │   └── sorting.test.js
│       └── 10-Settings/
│           └── settings.test.js
├── electron/
│   ├── clipboardCleanup.cjs
│   ├── clipboardCleanup.test.js
│   ├── clipboardCleanup.windows.ps1
│   ├── clipboardCleanupHelper.cjs
│   ├── clipboardCleanupHelper.test.js
│   ├── flatpak-paths.cjs
│   ├── flatpak-paths.test.js
│   ├── linuxWaylandClipboard.cjs
│   ├── linuxWaylandClipboardFallback.cjs
│   ├── linuxX11Clipboard.cjs
│   ├── linuxX11ClipboardFallback.cjs
│   ├── main.cjs
│   ├── preload.cjs
│   ├── preload.test.js
│   └── runtime-config.cjs
├── electron-builder.linux.json
├── electron-builder.mac.json
├── eslint.config.js
├── flatpak/
│   ├── appimage/
│   │   └── .gitkeep
│   ├── com.pears.pass.desktop
│   ├── com.pears.pass.metainfo.xml
│   └── com.pears.pass.yaml
├── forge.config.cjs
├── index.html
├── index.js
├── jest.config.js
├── lingui.config.js
├── package.json
├── resources/
│   └── bin/
│       ├── wl-copy-arm64
│       ├── wl-copy-x86_64
│       ├── wl-paste-arm64
│       ├── wl-paste-x86_64
│       ├── xsel-arm64
│       └── xsel-x86_64
├── scripts/
│   ├── afterPack.cjs
│   ├── apply-flavor.mjs
│   ├── build-flatpak.sh
│   ├── build-snap.sh
│   ├── build.worklet.mjs
│   ├── bundle-bridge.mjs
│   ├── bundle-renderer.mjs
│   ├── create-linux-tarball.sh
│   ├── notarize.cjs
│   └── patch-electron-dock-name.mjs
├── snap/
│   └── snapcraft.yaml
├── src/
│   ├── app/
│   │   ├── App/
│   │   │   ├── appConfig.js
│   │   │   ├── hooks/
│   │   │   │   ├── useInactivity.js
│   │   │   │   ├── useInactivity.test.js
│   │   │   │   ├── useOnExtension.test.js
│   │   │   │   ├── useOnExtensionExit.js
│   │   │   │   ├── useOnExtensionLockOut.js
│   │   │   │   ├── useOnExtensionLockOut.test.js
│   │   │   │   ├── useRedirect.js
│   │   │   │   └── useRedirect.test.js
│   │   │   ├── index.js
│   │   │   └── styles.js
│   │   └── Routes/
│   │       └── index.js
│   ├── components/
│   │   ├── AlertBox/
│   │   │   ├── index.tsx
│   │   │   └── styles.ts
│   │   ├── AppHeaderV2/
│   │   │   ├── AppHeaderV2.styles.ts
│   │   │   ├── AppHeaderV2.test.js
│   │   │   ├── AppHeaderV2.tsx
│   │   │   └── index.ts
│   │   ├── BackgroundWithGradient.tsx
│   │   ├── BadgeTextItem/
│   │   │   ├── index.js
│   │   │   ├── index.test.js
│   │   │   └── styles.js
│   │   ├── BannerBox/
│   │   │   ├── index.js
│   │   │   └── styles.js
│   │   ├── ButtonPlusCreateNew/
│   │   │   ├── index.js
│   │   │   ├── index.test.js
│   │   │   └── styles.js
│   │   ├── CardSingleSetting/
│   │   │   ├── index.js
│   │   │   ├── index.test.js
│   │   │   └── styles.js
│   │   ├── CopyButton/
│   │   │   └── index.tsx
│   │   ├── CreateCustomField/
│   │   │   ├── index.js
│   │   │   ├── index.test.js
│   │   │   └── styles.js
│   │   ├── CreateNewCategoryPopupContent/
│   │   │   ├── index.js
│   │   │   ├── index.test.js
│   │   │   └── styles.js
│   │   ├── DropdownSwapVault/
│   │   │   ├── index.test.js
│   │   │   ├── index.tsx
│   │   │   └── styles.ts
│   │   ├── EditFolderPopupContent/
│   │   │   ├── index.js
│   │   │   └── styles.js
│   │   ├── EmptyCollectionView/
│   │   │   ├── index.js
│   │   │   ├── index.test.js
│   │   │   └── styles.js
│   │   ├── FileDropArea/
│   │   │   ├── index.js
│   │   │   ├── index.test.js
│   │   │   └── styles.js
│   │   ├── FileUploadContent/
│   │   │   ├── index.js
│   │   │   └── styles.js
│   │   ├── FolderDropdown/
│   │   │   ├── FolderDropdownV2.tsx
│   │   │   ├── index.js
│   │   │   ├── index.test.js
│   │   │   └── styles.js
│   │   ├── FormGroup/
│   │   │   ├── index.js
│   │   │   ├── index.test.js
│   │   │   └── styles.js
│   │   ├── FormModalHeaderWrapper/
│   │   │   ├── index.js
│   │   │   ├── index.test.js
│   │   │   └── styles.js
│   │   ├── FormWrapper/
│   │   │   ├── index.js
│   │   │   └── index.test.js
│   │   ├── ImportDataOption/
│   │   │   ├── index.js
│   │   │   └── styles.js
│   │   ├── InitialPageWrapper/
│   │   │   ├── index.js
│   │   │   ├── index.test.js
│   │   │   └── styles.js
│   │   ├── InputFieldNote/
│   │   │   ├── index.js
│   │   │   └── index.test.js
│   │   ├── InputPearpassPassword/
│   │   │   ├── index.js
│   │   │   └── styles.js
│   │   ├── InputSearch/
│   │   │   ├── index.js
│   │   │   ├── index.test.js
│   │   │   └── styles.js
│   │   ├── ListItem/
│   │   │   ├── index.js
│   │   │   ├── index.test.js
│   │   │   └── styles.js
│   │   ├── LoadingOverlay/
│   │   │   ├── index.js
│   │   │   └── index.test.js
│   │   ├── MenuDropdown/
│   │   │   ├── MenuDropdownItem/
│   │   │   │   ├── index.js
│   │   │   │   └── index.test.js
│   │   │   ├── MenuDropdownLabel/
│   │   │   │   ├── index.js
│   │   │   │   └── index.test.js
│   │   │   ├── index.js
│   │   │   ├── index.test.js
│   │   │   └── styles.js
│   │   ├── NoticeContainer/
│   │   │   ├── index.js
│   │   │   └── styles.js
│   │   ├── OnboardingShell/
│   │   │   ├── index.tsx
│   │   │   └── styles.ts
│   │   ├── OtpCodeField/
│   │   │   ├── index.test.js
│   │   │   ├── index.ts
│   │   │   └── utils.ts
│   │   ├── OtpCodeFieldV2/
│   │   │   ├── index.test.tsx
│   │   │   ├── index.tsx
│   │   │   └── styles.ts
│   │   ├── Overlay/
│   │   │   ├── index.js
│   │   │   ├── index.test.js
│   │   │   └── styles.js
│   │   ├── PasswordFieldStrengthIndicator/
│   │   │   ├── index.test.tsx
│   │   │   └── index.tsx
│   │   ├── PopupMenu/
│   │   │   ├── index.js
│   │   │   ├── index.test.js
│   │   │   ├── styles.js
│   │   │   └── utils/
│   │   │       ├── getHorizontal.js
│   │   │       ├── getHorizontal.test.js
│   │   │       ├── getVertical.js
│   │   │       └── getVertical.test.js
│   │   ├── RadioSelect/
│   │   │   ├── index.js
│   │   │   ├── index.test.js
│   │   │   └── styles.js
│   │   ├── Record/
│   │   │   ├── index.js
│   │   │   ├── index.test.js
│   │   │   └── styles.js
│   │   ├── RecordActionsPopupContent/
│   │   │   ├── index.js
│   │   │   ├── index.test.js
│   │   │   └── styles.js
│   │   ├── RecordAvatar/
│   │   │   ├── index.test.js
│   │   │   ├── index.tsx
│   │   │   └── styles.ts
│   │   ├── RecordItemIcon/
│   │   │   ├── RecordItemIcon.styles.ts
│   │   │   ├── RecordItemIcon.tsx
│   │   │   └── index.ts
│   │   ├── RecordSortActionsPopupContent/
│   │   │   ├── index.js
│   │   │   ├── index.test.js
│   │   │   └── styles.js
│   │   ├── RecordTypeMenu/
│   │   │   ├── index.js
│   │   │   └── index.test.js
│   │   ├── Select/
│   │   │   ├── SelectItem/
│   │   │   │   ├── index.js
│   │   │   │   ├── index.test.js
│   │   │   │   └── styles.js
│   │   │   ├── SelectLabel/
│   │   │   │   ├── index.js
│   │   │   │   ├── index.test.js
│   │   │   │   └── styles.js
│   │   │   ├── index.js
│   │   │   ├── index.test.js
│   │   │   └── styles.js
│   │   ├── SidebarCategory/
│   │   │   ├── index.js
│   │   │   ├── index.test.js
│   │   │   └── styles.js
│   │   ├── SidebarFolder/
│   │   │   ├── index.js
│   │   │   ├── index.test.js
│   │   │   └── styles.js
│   │   ├── SidebarSearch/
│   │   │   ├── index.js
│   │   │   ├── index.test.js
│   │   │   └── styles.js
│   │   ├── SwitchWithLabel/
│   │   │   ├── index.js
│   │   │   ├── index.test.js
│   │   │   └── styles.js
│   │   ├── TimerBar/
│   │   │   ├── index.test.js
│   │   │   ├── index.ts
│   │   │   └── styles.ts
│   │   ├── TimerCircle/
│   │   │   ├── index.test.js
│   │   │   ├── index.ts
│   │   │   └── styles.ts
│   │   ├── TitleBar/
│   │   │   └── index.js
│   │   ├── Toasts/
│   │   │   ├── index.js
│   │   │   ├── index.test.js
│   │   │   └── styles.js
│   │   └── WebsiteButton/
│   │       └── index.tsx
│   ├── constants/
│   │   ├── appConstants.js
│   │   ├── feedback.js
│   │   ├── formFields.js
│   │   ├── layout.ts
│   │   ├── localStorage.js
│   │   ├── meta.js
│   │   ├── navigation.js
│   │   ├── pairing.js
│   │   ├── password.ts
│   │   ├── pearpassLinks.js
│   │   ├── recordActions.js
│   │   ├── recordColorByType.js
│   │   ├── recordIconByType.js
│   │   ├── securityErrors.js
│   │   ├── services.js
│   │   ├── sortOptions.ts
│   │   ├── timeConstants.js
│   │   └── transitions.js
│   ├── containers/
│   │   ├── AppHeaderContainer/
│   │   │   ├── AppHeaderContainer.test.js
│   │   │   ├── AppHeaderContainer.tsx
│   │   │   └── index.ts
│   │   ├── AttachmentField/
│   │   │   ├── index.js
│   │   │   └── styles.js
│   │   ├── AuthenticationCard/
│   │   │   ├── index.js
│   │   │   └── styles.js
│   │   ├── BaseInitialPage/
│   │   │   ├── index.js
│   │   │   └── styles.js
│   │   ├── CustomFields/
│   │   │   └── index.tsx
│   │   ├── EmptyCollectionViewV2/
│   │   │   ├── EmptyCollectionViewV2.styles.ts
│   │   │   ├── EmptyCollectionViewV2.tsx
│   │   │   └── index.ts
│   │   ├── EmptyResultsViewV2/
│   │   │   ├── EmptyResultsViewV2.styles.ts
│   │   │   ├── EmptyResultsViewV2.tsx
│   │   │   └── index.ts
│   │   ├── ImagesField/
│   │   │   ├── index.js
│   │   │   └── styles.js
│   │   ├── LayoutWithSidebar/
│   │   │   ├── index.js
│   │   │   └── styles.js
│   │   ├── MainViewHeader/
│   │   │   ├── MainViewHeader.styles.ts
│   │   │   ├── MainViewHeader.test.tsx
│   │   │   └── MainViewHeader.tsx
│   │   ├── Modal/
│   │   │   ├── AddDeviceModalContent/
│   │   │   │   ├── ScanQRExpireTimer.tsx
│   │   │   │   ├── index.ts
│   │   │   │   └── styles.ts
│   │   │   ├── AddDeviceModalContentV2/
│   │   │   │   ├── AddDeviceModalContentV2.styles.ts
│   │   │   │   └── AddDeviceModalContentV2.tsx
│   │   │   ├── AuthenticationModalContentV2/
│   │   │   │   ├── index.tsx
│   │   │   │   └── styles.ts
│   │   │   ├── BlindPeersModalContent/
│   │   │   │   ├── index.js
│   │   │   │   └── styles.js
│   │   │   ├── BrowserExtensionDialogV2/
│   │   │   │   ├── index.tsx
│   │   │   │   └── styles.ts
│   │   │   ├── ConfirmationModalContent/
│   │   │   │   ├── index.js
│   │   │   │   └── styles.js
│   │   │   ├── CreateFileEncryptionPassword/
│   │   │   │   ├── index.tsx
│   │   │   │   └── styles.ts
│   │   │   ├── CreateFolderModalContent/
│   │   │   │   ├── index.js
│   │   │   │   └── styles.js
│   │   │   ├── CreateFolderModalContentV2/
│   │   │   │   ├── CreateFolderModalContentV2.styles.ts
│   │   │   │   └── CreateFolderModalContentV2.tsx
│   │   │   ├── CreateOrEditCategoryWrapper/
│   │   │   │   ├── CreateOrEditAuthenticatorModalContent/
│   │   │   │   │   ├── CreateOrEditAuthenticatorModalContent.styles.ts
│   │   │   │   │   └── CreateOrEditAuthenticatorModalContent.tsx
│   │   │   │   ├── CreateOrEditCreditCardModalContent/
│   │   │   │   │   └── index.js
│   │   │   │   ├── CreateOrEditCreditCardModalContentV2/
│   │   │   │   │   ├── CreateOrEditCreditCardModalContentV2.styles.ts
│   │   │   │   │   └── CreateOrEditCreditCardModalContentV2.tsx
│   │   │   │   ├── CreateOrEditCustomModalContent/
│   │   │   │   │   └── index.js
│   │   │   │   ├── CreateOrEditCustomModalContentV2/
│   │   │   │   │   ├── CreateOrEditCustomModalContentV2.styles.ts
│   │   │   │   │   └── CreateOrEditCustomModalContentV2.tsx
│   │   │   │   ├── CreateOrEditIdentityModalContent/
│   │   │   │   │   └── index.js
│   │   │   │   ├── CreateOrEditIdentityModalContentV2/
│   │   │   │   │   ├── CreateOrEditIdentityModalContentV2.styles.ts
│   │   │   │   │   └── CreateOrEditIdentityModalContentV2.tsx
│   │   │   │   ├── CreateOrEditLoginModalContent/
│   │   │   │   │   └── index.js
│   │   │   │   ├── CreateOrEditLoginModalContentV2/
│   │   │   │   │   ├── CreateOrEditLoginModalContentV2.styles.ts
│   │   │   │   │   └── CreateOrEditLoginModalContentV2.tsx
│   │   │   │   ├── CreateOrEditNoteModalContent/
│   │   │   │   │   └── index.js
│   │   │   │   ├── CreateOrEditNoteModalContentV2/
│   │   │   │   │   ├── CreateOrEditNoteModalContentV2.styles.ts
│   │   │   │   │   └── CreateOrEditNoteModalContentV2.tsx
│   │   │   │   ├── CreateOrEditPassPhraseModalContent/
│   │   │   │   │   └── index.js
│   │   │   │   ├── CreateOrEditPassPhraseModalContentV2/
│   │   │   │   │   ├── CreateOrEditPassPhraseModalContentV2.styles.ts
│   │   │   │   │   └── CreateOrEditPassPhraseModalContentV2.tsx
│   │   │   │   ├── CreateOrEditWifiModalContent/
│   │   │   │   │   └── index.js
│   │   │   │   ├── CreateOrEditWifiModalContentV2/
│   │   │   │   │   ├── CreateOrEditWifiModalContentV2.styles.ts
│   │   │   │   │   └── CreateOrEditWifiModalContentV2.tsx
│   │   │   │   └── index.js
│   │   │   ├── CreateOrEditVaultModalContentV2/
│   │   │   │   ├── CreateOrEditVaultModalContentV2.styles.ts
│   │   │   │   └── CreateOrEditVaultModalContentV2.tsx
│   │   │   ├── CreateVaultModalContent/
│   │   │   │   ├── index.js
│   │   │   │   └── styles.js
│   │   │   ├── DecryptFilePassword/
│   │   │   │   └── index.tsx
│   │   │   ├── DeleteFolderModalContentV2/
│   │   │   │   ├── DeleteFolderModalContentV2.styles.ts
│   │   │   │   └── DeleteFolderModalContentV2.tsx
│   │   │   ├── DeleteRecordsModalContentV2/
│   │   │   │   ├── DeleteRecordsModalContentV2.styles.ts
│   │   │   │   ├── DeleteRecordsModalContentV2.tsx
│   │   │   │   └── index.ts
│   │   │   ├── DeleteVaultModalContent/
│   │   │   │   ├── DeviceList.tsx
│   │   │   │   ├── __tests__/
│   │   │   │   │   ├── DeleteVaultModalContent.test.tsx
│   │   │   │   │   └── DeviceList.test.tsx
│   │   │   │   ├── index.tsx
│   │   │   │   ├── styles.ts
│   │   │   │   └── types.ts
│   │   │   ├── DisplayPictureModalContent/
│   │   │   │   ├── index.js
│   │   │   │   └── styles.js
│   │   │   ├── DisplayPictureModalContentV2/
│   │   │   │   ├── DisplayPictureModalContentV2.styles.ts
│   │   │   │   └── DisplayPictureModalContentV2.tsx
│   │   │   ├── ExtensionPairingModalContent/
│   │   │   │   ├── ExtensionPairingModalContentV2.styles.ts
│   │   │   │   ├── ExtensionPairingModalContentV2.tsx
│   │   │   │   ├── index.js
│   │   │   │   └── styles.js
│   │   │   ├── GeneratePasswordModalContentV2/
│   │   │   │   ├── GeneratePasswordModalContentV2.styles.ts
│   │   │   │   └── GeneratePasswordModalContentV2.tsx
│   │   │   ├── GeneratePasswordSideDrawerContent/
│   │   │   │   ├── PassphraseChecker/
│   │   │   │   │   └── index.js
│   │   │   │   ├── PassphraseGenerator/
│   │   │   │   │   └── index..js
│   │   │   │   ├── PasswordChecker/
│   │   │   │   │   └── index.js
│   │   │   │   ├── PasswordGenerator/
│   │   │   │   │   └── index.js
│   │   │   │   ├── RuleSelector/
│   │   │   │   │   └── index.js
│   │   │   │   ├── index.js
│   │   │   │   └── styles.js
│   │   │   ├── ImportItemOrVaultModalContentV2/
│   │   │   │   ├── ImportItemOrVaultModalContentV2.styles.ts
│   │   │   │   └── index.tsx
│   │   │   ├── ImportVaultPreviewModalContent/
│   │   │   │   ├── ImportVaultPreviewModalContent.styles.ts
│   │   │   │   └── index.tsx
│   │   │   ├── ModalContent/
│   │   │   │   ├── index.js
│   │   │   │   └── styles.js
│   │   │   ├── ModalHeader/
│   │   │   │   ├── index.js
│   │   │   │   └── styles.js
│   │   │   ├── ModifyMasterVaultModalContent/
│   │   │   │   ├── index.js
│   │   │   │   └── styles.js
│   │   │   ├── ModifyVaultModalContent/
│   │   │   │   ├── index.js
│   │   │   │   └── styles.js
│   │   │   ├── MoveFolderModalContent/
│   │   │   │   ├── index.js
│   │   │   │   └── styles.js
│   │   │   ├── MoveFolderModalContentV2/
│   │   │   │   ├── MoveFolderModalContentV2.styles.ts
│   │   │   │   └── MoveFolderModalContentV2.tsx
│   │   │   ├── PairedDevicesModalContent/
│   │   │   │   ├── index.tsx
│   │   │   │   └── styles.ts
│   │   │   ├── SideDrawer/
│   │   │   │   ├── index.js
│   │   │   │   └── styles.js
│   │   │   ├── UnsavedChangesModalContent/
│   │   │   │   ├── UnsavedChangesModalContent.tsx
│   │   │   │   └── index.ts
│   │   │   ├── UpdateRequiredModalContent/
│   │   │   │   ├── index.js
│   │   │   │   └── styles.js
│   │   │   ├── UpdateRequiredModalContentV2/
│   │   │   │   ├── UpdateRequiredModalContentV2.styles.ts
│   │   │   │   └── UpdateRequiredModalContentV2.tsx
│   │   │   ├── UploadFilesModalContentV2/
│   │   │   │   ├── UploadFilesModalContentV2.styles.ts
│   │   │   │   ├── UploadFilesModalContentV2.tsx
│   │   │   │   └── index.ts
│   │   │   ├── UploadImageModalContent/
│   │   │   │   ├── index.js
│   │   │   │   └── styles.js
│   │   │   ├── VaultPasswordFormModalContent/
│   │   │   │   ├── index.js
│   │   │   │   └── styles.js
│   │   │   ├── index.js
│   │   │   └── styles.js
│   │   ├── MultiSelectActionsBar/
│   │   │   ├── MultiSelectActionsBar.styles.ts
│   │   │   ├── MultiSelectActionsBar.test.tsx
│   │   │   ├── MultiSelectActionsBar.tsx
│   │   │   └── index.ts
│   │   ├── PassPhrase/
│   │   │   ├── PassPhraseSettings.js
│   │   │   ├── PassPhraseV2.styles.ts
│   │   │   ├── PassPhraseV2.tsx
│   │   │   ├── __tests__/
│   │   │   │   ├── PassPhrase.test.js
│   │   │   │   └── PassPhraseSettings.test.js
│   │   │   ├── index.js
│   │   │   └── styles.js
│   │   ├── RecordDetails/
│   │   │   ├── CreditCardDetailsForm/
│   │   │   │   ├── CreditCardDetailsFormV2.styles.ts
│   │   │   │   ├── CreditCardDetailsFormV2.tsx
│   │   │   │   ├── index.js
│   │   │   │   └── utils.ts
│   │   │   ├── CustomDetailsForm/
│   │   │   │   ├── CustomDetailsFormV2.styles.ts
│   │   │   │   ├── CustomDetailsFormV2.tsx
│   │   │   │   └── index.js
│   │   │   ├── IdentityDetailsForm/
│   │   │   │   ├── IdentityDetailsFormV2.styles.ts
│   │   │   │   ├── IdentityDetailsFormV2.tsx
│   │   │   │   ├── index.js
│   │   │   │   └── utils.ts
│   │   │   ├── LoginRecordDetailsForm/
│   │   │   │   ├── LoginRecordDetailsFormV2.styles.ts
│   │   │   │   ├── LoginRecordDetailsFormV2.tsx
│   │   │   │   ├── index.js
│   │   │   │   └── utils.ts
│   │   │   ├── NoteDetailsForm/
│   │   │   │   ├── NoteDetailsFormV2.styles.ts
│   │   │   │   ├── NoteDetailsFormV2.tsx
│   │   │   │   ├── index.js
│   │   │   │   └── utils.ts
│   │   │   ├── PassPhraseDetailsForm/
│   │   │   │   ├── PassPhraseDetailsFormV2.styles.ts
│   │   │   │   ├── PassPhraseDetailsFormV2.tsx
│   │   │   │   ├── index.js
│   │   │   │   └── utils.ts
│   │   │   ├── RecordDetailsContent/
│   │   │   │   └── index.js
│   │   │   ├── RecordDetailsV2.styles.ts
│   │   │   ├── RecordDetailsV2.tsx
│   │   │   ├── WifiDetailsForm/
│   │   │   │   ├── WifiDetailsFormV2.styles.ts
│   │   │   │   ├── WifiDetailsFormV2.tsx
│   │   │   │   ├── index.js
│   │   │   │   └── utils.ts
│   │   │   ├── index.js
│   │   │   └── styles.js
│   │   ├── RecordListView/
│   │   │   ├── RecordListViewV2.styles.ts
│   │   │   ├── RecordListViewV2.test.tsx
│   │   │   ├── RecordListViewV2.tsx
│   │   │   ├── RecordRowContextMenuV2.styles.ts
│   │   │   ├── RecordRowContextMenuV2.tsx
│   │   │   ├── index.tsx
│   │   │   ├── styles.js
│   │   │   └── utils.js
│   │   ├── Sidebar/
│   │   │   ├── SidebarCategories/
│   │   │   │   ├── index.js
│   │   │   │   └── styles.js
│   │   │   ├── SidebarV2.styles.ts
│   │   │   ├── SidebarV2.test.tsx
│   │   │   ├── SidebarV2.tsx
│   │   │   ├── VaultSelector/
│   │   │   │   ├── VaultSelector.styles.ts
│   │   │   │   └── VaultSelector.tsx
│   │   │   ├── index.js
│   │   │   └── styles.js
│   │   └── WifiPasswordQRCode/
│   │       ├── WifiPasswordQRCodeV2.tsx
│   │       ├── index.js
│   │       └── styles.js
│   ├── context/
│   │   ├── AppHeaderContext.test.js
│   │   ├── AppHeaderContext.tsx
│   │   ├── BannerContext.js
│   │   ├── LoadingContext.js
│   │   ├── LoadingContext.test.js
│   │   ├── ModalContext.js
│   │   ├── ModalContext.test.js
│   │   ├── RouterContext.d.ts
│   │   ├── RouterContext.js
│   │   ├── RouterContext.test.js
│   │   ├── ToastContext.js
│   │   ├── ToastContext.test.js
│   │   └── UnsavedChangesContext.tsx
│   ├── electron/
│   │   ├── index.js
│   │   ├── vaultClientProxy.js
│   │   └── vaultClientProxy.test.js
│   ├── hooks/
│   │   ├── __tests__/
│   │   │   └── useTranslation.test.js
│   │   ├── useAnimatedVisibility.js
│   │   ├── useAnimatedVisibility.test.js
│   │   ├── useAutoLockPreferences.test.js
│   │   ├── useAutoLockPreferences.ts
│   │   ├── useConnectExtension.js
│   │   ├── useConnectExtension.test.js
│   │   ├── useCopyToClipboard.electron.js
│   │   ├── useCopyToClipboard.electron.test.js
│   │   ├── useCreateOrEditRecord.d.ts
│   │   ├── useCreateOrEditRecord.js
│   │   ├── useCreateOrEditRecord.test.js
│   │   ├── useCustomFields.js
│   │   ├── useCustomFields.test.js
│   │   ├── useGetMultipleFiles.js
│   │   ├── useGetMultipleFiles.test.js
│   │   ├── useLanguageOptions.js
│   │   ├── useLanguageOptions.test.js
│   │   ├── useOutsideClick.js
│   │   ├── useOutsideClick.test.js
│   │   ├── usePasteFromClipboard.js
│   │   ├── usePasteFromClipboard.test.js
│   │   ├── usePearUpdate.js
│   │   ├── usePearUpdate.test.js
│   │   ├── useRecordActionItems.d.ts
│   │   ├── useRecordActionItems.js
│   │   ├── useRecordActionItems.test.js
│   │   ├── useRecordMenuItems.js
│   │   ├── useRecordMenuItems.test.js
│   │   ├── useRecordMenuItemsV2.test.ts
│   │   ├── useRecordMenuItemsV2.ts
│   │   ├── useRiveWithRetry.ts
│   │   ├── useScrollOverflow.test.js
│   │   ├── useScrollOverflow.ts
│   │   ├── useSimulatedLoading.js
│   │   ├── useSimulatedLoading.test.js
│   │   ├── useTranslation.ts
│   │   ├── useVaultSwitch.test.tsx
│   │   ├── useVaultSwitch.tsx
│   │   ├── useWindowResize.js
│   │   └── useWindowResize.test.js
│   ├── lib-react-components/
│   │   ├── components/
│   │   │   ├── ButtonCreate/
│   │   │   │   ├── index.js
│   │   │   │   ├── index.test.js
│   │   │   │   └── styles.js
│   │   │   ├── ButtonFilter/
│   │   │   │   ├── index.js
│   │   │   │   ├── index.test.js
│   │   │   │   └── styles.js
│   │   │   ├── ButtonFolder/
│   │   │   │   ├── index.js
│   │   │   │   ├── index.test.js
│   │   │   │   └── styles.js
│   │   │   ├── ButtonLittle/
│   │   │   │   ├── index.js
│   │   │   │   ├── index.test.js
│   │   │   │   └── styles.js
│   │   │   ├── ButtonPrimary/
│   │   │   │   ├── index.js
│   │   │   │   ├── index.test.js
│   │   │   │   └── styles.js
│   │   │   ├── ButtonRadio/
│   │   │   │   ├── index.js
│   │   │   │   ├── index.test.js
│   │   │   │   └── styles.js
│   │   │   ├── ButtonRoundIcon/
│   │   │   │   ├── index.js
│   │   │   │   ├── index.test.js
│   │   │   │   └── styles.js
│   │   │   ├── ButtonSecondary/
│   │   │   │   ├── index.js
│   │   │   │   ├── index.test.js
│   │   │   │   └── styles.js
│   │   │   ├── ButtonSingleInput/
│   │   │   │   ├── index.js
│   │   │   │   ├── index.test.js
│   │   │   │   └── styles.js
│   │   │   ├── ButtonThin/
│   │   │   │   ├── index.js
│   │   │   │   ├── index.test.js
│   │   │   │   └── styles.js
│   │   │   ├── CompoundField/
│   │   │   │   ├── index.js
│   │   │   │   ├── index.test.js
│   │   │   │   └── styles.js
│   │   │   ├── HighlightString/
│   │   │   │   ├── index.js
│   │   │   │   ├── index.test.js
│   │   │   │   └── styles.js
│   │   │   ├── InputField/
│   │   │   │   ├── index.test.js
│   │   │   │   ├── index.tsx
│   │   │   │   └── styles.ts
│   │   │   ├── NoticeText/
│   │   │   │   ├── index.js
│   │   │   │   ├── index.test.js
│   │   │   │   └── styles.js
│   │   │   ├── PasswordField/
│   │   │   │   ├── index.js
│   │   │   │   ├── index.test.js
│   │   │   │   └── styles.js
│   │   │   ├── PearPassInputField/
│   │   │   │   ├── index.js
│   │   │   │   ├── index.test.js
│   │   │   │   └── styles.js
│   │   │   ├── PearPassPasswordField/
│   │   │   │   ├── index.js
│   │   │   │   ├── index.test.js
│   │   │   │   └── styles.js
│   │   │   ├── PearPassPasswordFieldV2/
│   │   │   │   ├── index.test.ts
│   │   │   │   ├── index.tsx
│   │   │   │   ├── styles.ts
│   │   │   │   └── types.ts
│   │   │   ├── Slider/
│   │   │   │   ├── index.js
│   │   │   │   ├── index.test.js
│   │   │   │   └── styles.js
│   │   │   ├── Switch/
│   │   │   │   ├── index.js
│   │   │   │   ├── index.test.js
│   │   │   │   └── styles.js
│   │   │   ├── TextArea/
│   │   │   │   ├── index.test.js
│   │   │   │   ├── index.tsx
│   │   │   │   └── styles.ts
│   │   │   └── TooltipWrapper/
│   │   │       ├── index.js
│   │   │       └── styles.js
│   │   ├── icons/
│   │   │   ├── AboutIcon/
│   │   │   │   └── index.js
│   │   │   ├── AppearanceIcon/
│   │   │   │   └── index.js
│   │   │   ├── ArrowDownIcon/
│   │   │   │   └── index.js
│   │   │   ├── ArrowLeftIcon/
│   │   │   │   └── index.js
│   │   │   ├── ArrowRightIcon/
│   │   │   │   └── index.js
│   │   │   ├── ArrowUpAndDown/
│   │   │   │   └── index.js
│   │   │   ├── ArrowUpIcon/
│   │   │   │   └── index.js
│   │   │   ├── AutoFillIcon/
│   │   │   │   └── index.js
│   │   │   ├── BackIcon/
│   │   │   │   └── index.js
│   │   │   ├── BrushIcon/
│   │   │   │   └── index.js
│   │   │   ├── CalendarIcon/
│   │   │   │   └── index.js
│   │   │   ├── CheckIcon/
│   │   │   │   └── index.js
│   │   │   ├── CollapseIcon/
│   │   │   │   └── index.js
│   │   │   ├── CommonFileIcon/
│   │   │   │   └── index.js
│   │   │   ├── ComputerIcon/
│   │   │   │   └── index.js
│   │   │   ├── CopyIcon/
│   │   │   │   └── index.js
│   │   │   ├── CreditCardIcon/
│   │   │   │   └── index.js
│   │   │   ├── CubeIcon/
│   │   │   │   └── index.js
│   │   │   ├── DeleteIcon/
│   │   │   │   └── index.js
│   │   │   ├── EmailIcon/
│   │   │   │   └── index.js
│   │   │   ├── ErrorIcon/
│   │   │   │   └── index.js
│   │   │   ├── ExitIcon/
│   │   │   │   └── index.js
│   │   │   ├── EyeClosedIcon/
│   │   │   │   └── index.js
│   │   │   ├── EyeIcon/
│   │   │   │   └── index.js
│   │   │   ├── FolderIcon/
│   │   │   │   └── index.js
│   │   │   ├── FullBodyIcon/
│   │   │   │   └── index.js
│   │   │   ├── GenderIcon/
│   │   │   │   └── index.js
│   │   │   ├── GroupIcon/
│   │   │   │   └── index.js
│   │   │   ├── ImageIcon/
│   │   │   │   └── index.js
│   │   │   ├── InfoIcon/
│   │   │   │   └── index.js
│   │   │   ├── KebabMenuIcon/
│   │   │   │   └── index.js
│   │   │   ├── KeyIcon/
│   │   │   │   └── index.js
│   │   │   ├── LockCircleIcon/
│   │   │   │   └── index.js
│   │   │   ├── LockIcon/
│   │   │   │   └── index.js
│   │   │   ├── MoveToIcon/
│   │   │   │   └── index.js
│   │   │   ├── MultiSelectionIcon/
│   │   │   │   └── index.js
│   │   │   ├── NationalityIcon/
│   │   │   │   └── index.js
│   │   │   ├── NewFolderIcon/
│   │   │   │   └── index.js
│   │   │   ├── NineDotsIcon/
│   │   │   │   └── index.js
│   │   │   ├── NoteIcon/
│   │   │   │   └── index.js
│   │   │   ├── OkayIcon/
│   │   │   │   └── index.js
│   │   │   ├── OutsideLinkIcon/
│   │   │   │   └── index.js
│   │   │   ├── PassPhraseIcon/
│   │   │   │   └── index.js
│   │   │   ├── PasswordIcon/
│   │   │   │   └── index.js
│   │   │   ├── PasteIcon/
│   │   │   │   └── index.js
│   │   │   ├── PhoneIcon/
│   │   │   │   └── index.js
│   │   │   ├── PinIcon/
│   │   │   │   └── index.js
│   │   │   ├── PlusIcon/
│   │   │   │   └── index.js
│   │   │   ├── SaveIcon/
│   │   │   │   └── index.js
│   │   │   ├── SearchIcon/
│   │   │   │   └── index.js
│   │   │   ├── SecurityIcon/
│   │   │   │   └── index.js
│   │   │   ├── SettingsIcon/
│   │   │   │   └── index.js
│   │   │   ├── ShareIcon/
│   │   │   │   └── index.js
│   │   │   ├── SmallArrowIcon/
│   │   │   │   └── index.js
│   │   │   ├── StarIcon/
│   │   │   │   └── index.js
│   │   │   ├── SyncingIcon/
│   │   │   │   └── index.js
│   │   │   ├── TimeIcon/
│   │   │   │   └── index.js
│   │   │   ├── UserIcon/
│   │   │   │   └── index.js
│   │   │   ├── UserSecurityIcon/
│   │   │   │   └── index.js
│   │   │   ├── VaultIcon/
│   │   │   │   └── index.js
│   │   │   ├── WifiIcon/
│   │   │   │   └── index.js
│   │   │   ├── WorldIcon/
│   │   │   │   └── index.js
│   │   │   ├── XIcon/
│   │   │   │   └── index.js
│   │   │   ├── YellowErrorIcon/
│   │   │   │   └── index.js
│   │   │   └── icons.test.js
│   │   ├── illustrations/
│   │   │   └── AuthenticatorIllustration/
│   │   │       └── index.js
│   │   ├── index.js
│   │   └── utils/
│   │       ├── getIconProps.js
│   │       └── getIconProps.test.js
│   ├── pages/
│   │   ├── AuthenticatorView/
│   │   │   ├── index.js
│   │   │   ├── index.test.js
│   │   │   └── styles.ts
│   │   ├── InitialPage/
│   │   │   └── index.js
│   │   ├── Intro/
│   │   │   ├── CategoryAnimation/
│   │   │   │   └── index.tsx
│   │   │   ├── CreditCardAnimation/
│   │   │   │   └── index.tsx
│   │   │   ├── GradientContainer/
│   │   │   │   ├── index.js
│   │   │   │   └── styles.js
│   │   │   ├── IntroV2.tsx
│   │   │   ├── IntroV2Styles.ts
│   │   │   ├── OnboardingLockVideo/
│   │   │   │   └── index.tsx
│   │   │   ├── PasswordFillAnimation/
│   │   │   │   ├── index.tsx
│   │   │   │   └── styles.js
│   │   │   ├── SyncWithoutCloudAnimation/
│   │   │   │   └── index.tsx
│   │   │   ├── TutorialContainer/
│   │   │   │   ├── index.js
│   │   │   │   └── styles.js
│   │   │   ├── WelcomeToPearpass/
│   │   │   │   ├── index.js
│   │   │   │   └── styles.js
│   │   │   ├── index.js
│   │   │   └── styles.js
│   │   ├── LoadingPage/
│   │   │   ├── LoadingPageV2.tsx
│   │   │   ├── LoadingPageV2Styles.ts
│   │   │   ├── index.js
│   │   │   └── styles.js
│   │   ├── MainView/
│   │   │   ├── MainViewV2.styles.ts
│   │   │   ├── MainViewV2.tsx
│   │   │   ├── index.js
│   │   │   └── styles.js
│   │   ├── SettingsView/
│   │   │   ├── AboutContent/
│   │   │   │   ├── index.js
│   │   │   │   └── index.test.js
│   │   │   ├── AppearanceContent/
│   │   │   │   └── index.js
│   │   │   ├── ExportTab/
│   │   │   │   ├── index.js
│   │   │   │   ├── styles.js
│   │   │   │   └── utils/
│   │   │   │       ├── downloadFile.js
│   │   │   │       ├── downloadFile.test.js
│   │   │   │       ├── downloadZip.js
│   │   │   │       ├── downloadZip.test.js
│   │   │   │       ├── exportCsvPerVault.js
│   │   │   │       └── exportJsonPerVault.js
│   │   │   ├── ImportTab/
│   │   │   │   ├── index.js
│   │   │   │   ├── styles.js
│   │   │   │   └── utils/
│   │   │   │       ├── readFileContent.js
│   │   │   │       └── readFileContent.test.js
│   │   │   ├── SecurityContent/
│   │   │   │   └── index.js
│   │   │   ├── SettingsAdvancedTab/
│   │   │   │   ├── SettingsAutoLockConfiguration/
│   │   │   │   │   ├── index.tsx
│   │   │   │   │   └── styles.ts
│   │   │   │   ├── SettingsBlindPeersSection/
│   │   │   │   │   ├── index.js
│   │   │   │   │   ├── index.test.js
│   │   │   │   │   └── styles.js
│   │   │   │   ├── index.js
│   │   │   │   └── styles.js
│   │   │   ├── SettingsTab/
│   │   │   │   ├── SettingsDevicesSection/
│   │   │   │   │   ├── index.js
│   │   │   │   │   ├── index.test.js
│   │   │   │   │   └── styles.js
│   │   │   │   ├── SettingsLanguageSection/
│   │   │   │   │   ├── index.js
│   │   │   │   │   ├── index.test.js
│   │   │   │   │   └── styles.js
│   │   │   │   ├── SettingsPasswordsSection/
│   │   │   │   │   ├── index.js
│   │   │   │   │   └── styles.js
│   │   │   │   ├── SettingsReportSection/
│   │   │   │   │   ├── index.js
│   │   │   │   │   ├── index.test.js
│   │   │   │   │   └── styles.js
│   │   │   │   ├── index.js
│   │   │   │   └── styles.js
│   │   │   ├── SettingsVaultsTab/
│   │   │   │   ├── index.js
│   │   │   │   ├── index.test.js
│   │   │   │   └── styles.js
│   │   │   ├── SyncingContent/
│   │   │   │   └── index.js
│   │   │   ├── VaultContent/
│   │   │   │   └── index.js
│   │   │   ├── index.js
│   │   │   └── styles.js
│   │   ├── SettingsViewV2/
│   │   │   ├── SettingsViewV2.styles.ts
│   │   │   ├── SettingsViewV2.tsx
│   │   │   └── content/
│   │   │       ├── AppPreferencesContent/
│   │   │       │   ├── index.test.tsx
│   │   │       │   ├── index.tsx
│   │   │       │   └── styles.ts
│   │   │       ├── AppVersionContent/
│   │   │       │   ├── index.tsx
│   │   │       │   └── styles.ts
│   │   │       ├── BlindPeersContent/
│   │   │       │   ├── index.test.tsx
│   │   │       │   ├── index.tsx
│   │   │       │   └── styles.ts
│   │   │       ├── DiagnosticsContent/
│   │   │       │   ├── index.test.tsx
│   │   │       │   ├── index.tsx
│   │   │       │   └── styles.ts
│   │   │       ├── ExportItemsContent/
│   │   │       │   ├── index.test.tsx
│   │   │       │   ├── index.tsx
│   │   │       │   └── styles.ts
│   │   │       ├── ImportCodesContent/
│   │   │       │   ├── index.tsx
│   │   │       │   └── styles.ts
│   │   │       ├── ImportItemsContent/
│   │   │       │   ├── index.test.tsx
│   │   │       │   ├── index.tsx
│   │   │       │   └── styles.ts
│   │   │       ├── LanguageContent/
│   │   │       │   ├── index.tsx
│   │   │       │   └── styles.ts
│   │   │       ├── MasterPasswordContent/
│   │   │       │   ├── index.test.tsx
│   │   │       │   ├── index.tsx
│   │   │       │   └── styles.ts
│   │   │       ├── ReportAProblemContent/
│   │   │       │   ├── index.test.tsx
│   │   │       │   ├── index.tsx
│   │   │       │   └── styles.ts
│   │   │       ├── YourDevicesContent/
│   │   │       │   ├── index.test.tsx
│   │   │       │   ├── index.tsx
│   │   │       │   └── styles.ts
│   │   │       ├── YourVaultsContent/
│   │   │       │   ├── index.test.tsx
│   │   │       │   ├── index.tsx
│   │   │       │   └── styles.ts
│   │   │       └── index.ts
│   │   └── WelcomePage/
│   │       ├── CardCreateMasterPassword/
│   │       │   ├── index.js
│   │       │   └── styles.js
│   │       ├── CardCreateMasterPasswordV2/
│   │       │   ├── index.tsx
│   │       │   └── styles.ts
│   │       ├── CardLoadVault/
│   │       │   ├── index.js
│   │       │   └── styles.js
│   │       ├── CardNewVaultCredentials/
│   │       │   ├── index.js
│   │       │   └── styles.js
│   │       ├── CardUnlockPearPass/
│   │       │   └── index.tsx
│   │       ├── CardUnlockPearPassV2/
│   │       │   ├── index.tsx
│   │       │   └── styles.ts
│   │       ├── CardUnlockVault/
│   │       │   ├── index.js
│   │       │   └── styles.js
│   │       ├── CardUploadBackupFile/
│   │       │   ├── index.js
│   │       │   └── styles.js
│   │       ├── CardVaultSelect/
│   │       │   ├── index.js
│   │       │   └── styles.js
│   │       ├── LockedScreen/
│   │       │   ├── Timer.js
│   │       │   ├── index.js
│   │       │   └── styles.js
│   │       ├── LockedScreenV2/
│   │       │   ├── LockedScreenV2.styles.ts
│   │       │   ├── LockedScreenV2.test.tsx
│   │       │   └── LockedScreenV2.tsx
│   │       ├── index.js
│   │       └── styles.js
│   ├── services/
│   │   ├── createOrGetPearpassClient.js
│   │   ├── createOrGetPearpassClient.test.js
│   │   ├── createOrGetPipe.js
│   │   ├── createOrGetPipe.test.js
│   │   ├── handlers/
│   │   │   ├── EncryptionHandlers.js
│   │   │   ├── EncryptionHandlers.test.js
│   │   │   ├── SecureRequestHandler.js
│   │   │   ├── SecureRequestHandler.test.js
│   │   │   ├── SecurityHandlers.js
│   │   │   ├── SecurityHandlers.test.js
│   │   │   └── VaultHandlers.js
│   │   ├── ipc/
│   │   │   ├── MethodRegistry.js
│   │   │   ├── MethodRegistry.test.js
│   │   │   ├── SocketManager.js
│   │   │   └── SocketManager.test.js
│   │   ├── nativeMessagingIPCServer.js
│   │   ├── nativeMessagingIPCServer.test.js
│   │   ├── nativeMessagingPreferences.js
│   │   ├── nativeMessagingPreferences.test.js
│   │   └── security/
│   │       ├── appIdentity.js
│   │       ├── appIdentity.test.js
│   │       ├── protocolConstants.js
│   │       ├── sessionManager.js
│   │       ├── sessionManager.test.js
│   │       └── sessionStore.js
│   ├── shared/
│   │   ├── commandDefinitions.js
│   │   └── types.ts
│   ├── strict.css
│   ├── svgs/
│   │   ├── ItemCardIllustration/
│   │   │   ├── ItemCardIllustration.tsx
│   │   │   └── index.ts
│   │   ├── LogoLock/
│   │   │   ├── index.js
│   │   │   └── index.test.js
│   │   ├── OnboardingLock/
│   │   │   └── index.js
│   │   ├── PearLogo/
│   │   │   └── index.js
│   │   ├── PearpassLogo/
│   │   │   └── index.js
│   │   ├── ProtonPass/
│   │   │   └── index.js
│   │   ├── SpotlightLeft/
│   │   │   ├── index.js
│   │   │   └── index.test.js
│   │   ├── SpotlightMiddle/
│   │   │   ├── index.js
│   │   │   └── index.test.js
│   │   └── SpotlightRight/
│   │       ├── index.js
│   │       └── index.test.js
│   ├── types/
│   │   ├── css.d.ts
│   │   ├── electron.d.ts
│   │   ├── jest-globals.d.ts
│   │   ├── modules.d.ts
│   │   └── styled.d.ts
│   └── utils/
│       ├── addHttps.js
│       ├── addHttps.test.js
│       ├── applyGlobalStyles.js
│       ├── applyGlobalStyles.test.js
│       ├── autoLock.js
│       ├── autoLock.test.js
│       ├── breakpoints.js
│       ├── breakpoints.test.js
│       ├── createErrorWithCode.js
│       ├── createErrorWithCode.test.js
│       ├── designVersion.js
│       ├── devicePreferences.cjs
│       ├── devicePreferences.test.js
│       ├── envGetter.js
│       ├── envGetter.test.js
│       ├── extractDomainName.js
│       ├── extractDomainName.test.js
│       ├── formatPasskeyDate.js
│       ├── getDeviceName.js
│       ├── getDeviceName.test.js
│       ├── getFilteredAttachmentsById.js
│       ├── getFilteredAttachmentsById.test.js
│       ├── getPasswordStrengthInfo.test.ts
│       ├── getPasswordStrengthInfo.ts
│       ├── getRecordSubtitle.test.ts
│       ├── getRecordSubtitle.ts
│       ├── groupRecordsByTimePeriod.test.ts
│       ├── groupRecordsByTimePeriod.ts
│       ├── handleFileSelect.js
│       ├── handleFileSelect.test.js
│       ├── isFavorite.js
│       ├── isFavorite.test.js
│       ├── isOnline.js
│       ├── isPasswordChangeReminderDisabled.js
│       ├── isPasswordChangeReminderDisabled.test.js
│       ├── logHelper.cjs
│       ├── logHelper.test.js
│       ├── logger.js
│       ├── logger.test.js
│       ├── nativeMessagingSetup.js
│       ├── nativeMessagingSetup.test.js
│       ├── sortByName.js
│       ├── sortByName.test.js
│       ├── toSentenceCase.js
│       ├── toSentenceCase.test.js
│       ├── vaultCreated.js
│       ├── vaultCreated.test.js
│       ├── withAlpha.test.js
│       └── withAlpha.ts
├── styles.js
└── tsconfig.json

================================================
FILE CONTENTS
================================================

================================================
FILE: .claude/skills/use-ui-kit/SKILL.md
================================================
---
name: use-ui-kit
description: Use whenever creating or editing UI in this repo — React components, modals, dialogs, forms, buttons, inputs, typography, styling, icons, or any .tsx/.jsx work. The repo uses `@tetherto/pearpass-lib-ui-kit` as the single source for UI primitives; do not roll custom ones. Load this before suggesting any UI change, especially for v2 files (V2 suffix) or when touching src/components, src/containers/Modal, or src/pages.
---

<!-- Mirror of AGENTS.md at repo root — keep in sync. AGENTS.md is the canonical copy; this file exists so Claude Code's skill-trigger mechanism can lazy-load the same content on UI work. -->

# UI conventions for pearpass-app-desktop-tether

This is the Electron desktop app for PearPass. It's written in React + TypeScript. UI is built on the shared component library `@tetherto/pearpass-lib-ui-kit`.

This document is for **anyone contributing UI** to the repo — new hires, current engineers, and AI coding assistants (Claude Code, Cursor, Codex, etc.). It captures the component catalog, styling conventions, file-naming rules, and patterns we use when building UI in this app. Read it once before your first UI change; keep it open when you're in doubt.

## Design-system state — `DESKTOP_DESIGN_VERSION === 2`

Which design renders at runtime is controlled by the `DESKTOP_DESIGN_VERSION` flag from `@tetherto/pearpass-lib-constants` (resolved in [electron/runtime-config.cjs](../../../electron/runtime-config.cjs) and exposed via `isV2()` in [src/utils/designVersion.js](../../../src/utils/designVersion.js)).

**Currently `DESKTOP_DESIGN_VERSION === 2`**, so kit components are the right choice for all new UI work. Legacy v1 components still live under [src/lib-react-components/](../../../src/lib-react-components/) and are rendered whenever `isV2()` returns `false` — do not delete them as part of v2 work.

## File naming: when to use the `V2` suffix

The `V2` suffix is a **coexistence marker**, not a design marker. Use it only when a v1 sibling already exists:

- **A v1 file already exists** for this component/screen → create a new file with the `V2` suffix next to it (e.g. v1 `CreateVaultModalContent.jsx` → new `CreateVaultModalContentV2.tsx`). Both live in the tree during migration; the branching happens at the call site via `isV2()`.
- **No v1 equivalent exists** (net-new feature, net-new component) → create the file with its natural name, **no `V2` suffix**. The codebase is already v2 by default, so the suffix would just be noise.

Before creating a file, glob the directory for the base name without the suffix. If nothing comes up, skip the suffix.

## Golden rules

1. **Check the catalog below before creating any component.** If it exists in the kit, use it — never wrap or reimplement.
2. **All new UI goes through the kit.** Any new `.tsx`/`.jsx` file — suffixed or not — must import from `@tetherto/pearpass-lib-ui-kit`, not from [src/lib-react-components/](../../../src/lib-react-components/).
3. **Never add variants under [src/lib-react-components/components/](../../../src/lib-react-components/components/)** (`ButtonThin`, `ButtonPrimary`, `ButtonRoundIcon`, `PearPassInputField`, etc.). That tree is legacy; the kit's `Button` takes variants.
4. **Style with tokens.** Use `useTheme()` + `rawTokens`. No hardcoded hex colors or px spacing.
5. **Icons come from the kit.** `@tetherto/pearpass-lib-ui-kit/icons` has 530 icons. Do not add new SVGs under `src/`.
6. **If the kit lacks something you need, stop and ask the user.** Don't silently roll a custom component.

## Component catalog (31 components)

Import pattern: `import { ComponentName } from '@tetherto/pearpass-lib-ui-kit'`

### Actions
- `Button` — all CTAs. Takes variants; use instead of `ButtonThin`, `ButtonPrimary`, `ButtonSecondary`, `ButtonRoundIcon`, `ButtonLittle`, `ButtonFilter`, `ButtonFolder`, `ButtonRadio`, `ButtonSingleInput`, `ButtonCreate`.
- `Pressable` — low-level pressable wrapper for custom interactive elements.
- `Link` — text links.

### Forms
- `Form` — form wrapper with validation.
- `InputField` — text input. Use instead of `PearPassInputField`.
- `PasswordField` — password input with strength indicator. Use instead of `PearPassPasswordField` / `PearPassPasswordFieldV2`.
- `SearchField` — search input.
- `SelectField` — dropdown select.
- `Dropdown` — low-level dropdown primitive.
- `TextArea` — multiline text input.
- `Checkbox`
- `Radio`
- `ToggleSwitch`
- `Slider`
- `DateField`
- `AttachmentField`
- `UploadField`
- `MultiSlotInput` — split inputs for OTP / recovery codes. Use instead of custom `OtpCodeField`.
- `FieldError` — inline field validation error.

### Typography
- `Title` — headings.
- `Text` — body text.

### Layout / surfaces
- `Dialog` — modals. Use instead of custom `ModalContent` wrappers.
- `NativeBottomSheet` — bottom sheets.
- `PageHeader` — top-of-page header.
- `ItemScreenHeader` — item-detail header.
- `Breadcrumb`
- `ListItem`
- `NavbarListItem`
- `ContextMenu`

### Feedback
- `AlertMessage` — inline alerts. Reference: [src/pages/WelcomePage/CardCreateMasterPasswordV2/index.tsx](../../../src/pages/WelcomePage/CardCreateMasterPasswordV2/index.tsx).
- `Snackbar` — toast-style notifications.
- `PasswordIndicator` — standalone password strength meter.

### Type exports
- `ThemeColors`, `Theme`, `ThemeType`, `RawTokens`
- `PasswordIndicatorVariant` — `'vulnerable' | 'decent' | 'strong'`

Import types with `import type { ... } from '@tetherto/pearpass-lib-ui-kit'`.

## Component props (15 most-used)

Required props have no `?`. **Always include a test ID on interactive components** — see the "Test IDs" section below for which prop to use per component.

- **Button** — `variant: 'primary' | 'secondary' | 'tertiary' | 'destructive'`, `size: 'small' | 'medium'`, `onClick`, `children`, `type?: 'button' | 'submit'`, `disabled?`, `isLoading?`, `iconBefore?`, `iconAfter?`, `data-testid?`. Icon-only buttons need `aria-label`.
- **Dialog** — `title` (ReactNode), `onClose?`, `open?`, `footer?`, `children?`, `closeOnOutsideClick?`, `hideCloseButton?`, `trapFocus?`, `initialFocusRef?`, `testID?`, `closeButtonTestID?`. Put action buttons in `footer`.
- **InputField** — `label`, `value`, `onChange?: (e) => void`, `placeholder?`, `error?: string`, `inputType?: 'text' | 'password'`, `disabled?`, `readOnly?`, `copyable?`, `onCopy?`, `leftSlot?`, `rightSlot?`, `testID?`.
- **PasswordField** — `label`, `value`, `onChange?`, `placeholder?`, `error?`, `passwordIndicator?: 'vulnerable' | 'decent' | 'strong' | 'match'`, `infoBox?: string`, `copyable?`, `testID?`.
- **SearchField** — `value`, `onChangeText` (yes, this one is still current), `placeholderText?`, `size?: 'small' | 'medium'`, `testID?`.
- **Form** — `children`, `onSubmit?`, `noValidate?`, `testID?`. Wrap fields here; pair with `useForm` from `@tetherto/pear-apps-lib-ui-react-hooks`.
- **Text** — `children`, `as?: 'p' | 'span'`, `variant?: 'label' | 'labelEmphasized' | 'body' | 'bodyEmphasized' | 'caption'`, `color?`, `numberOfLines?`, `data-testid?`.
- **Title** — `children`, `as?: 'h1' | 'h2' | ... | 'h6'`, `data-testid?`.
- **AlertMessage** — `variant: 'info' | 'warning' | 'error'`, `size: 'small' | 'medium' | 'big'`, `title`, `description`, `actionText?`, `onAction?`, `testID?`, `actionTestId?`.
- **ToggleSwitch** — `checked?`, `onChange?: (b: boolean) => void`, `label?`, `description?`, `disabled?`, `data-testid?`.
- **Checkbox** — same shape as `ToggleSwitch` (uses `data-testid`).
- **Radio** — `options: Array<{value, label?, description?, disabled?}>`, `value?`, `onChange?: (v: string) => void`, `testID?`.
- **SelectField** — `label`, `value?`, `placeholder?`, `onClick?` (opens dropdown), `error?`, `disabled?`, `leftSlot?`, `rightSlot?`, `testID?`.
- **TextArea** — `value`, `onChange?`, `label?`, `placeholder?`, `error?`, `disabled?`, `testID?`.
- **Link** — `children`, `href?`, `isExternal?`, `onClick?`, `data-testid?` (and standard `<a>` attributes).

For components not listed, open `node_modules/@tetherto/pearpass-lib-ui-kit/dist/components/<Name>/types.d.ts`.

### Test IDs — `testID` vs `data-testid`

Always include a test ID on anything a user interacts with (buttons, fields, toggles, dialogs). Which prop depends on the component:

- **`testID`** — components that declare it explicitly: `Dialog` (+ `closeButtonTestID`), `Form`, `InputField`, `PasswordField`, `SearchField`, `SelectField`, `TextArea`, `Radio`, `AlertMessage` (+ `actionTestId`).
- **`data-testid`** — components that extend native HTML and don't redeclare it: `Button`, `ToggleSwitch`, `Checkbox`, `Link`, `Pressable`, `Text`, `Title`.

Rule of thumb: try `testID` first; if TypeScript rejects it, use `data-testid`. When editing an existing file, follow the naming pattern already there (e.g. `createvault-name-v2`, `createvault-discard-v2`).

### Prop naming — modern vs. deprecated (important)

The kit recently renamed several field props. **Use the modern names:**

| Use | Not (deprecated) |
| --- | --- |
| `onChange` (receives `ChangeEvent`) | `onChangeText` (receives string) |
| `placeholder` | `placeholderText` |
| `error` (string) | `errorMessage` + `variant` |

⚠️ **Existing v2 files in this repo (e.g. `CreateVaultModalContentV2`, `CardCreateMasterPasswordV2`) still use the deprecated props.** Don't copy their prop names blindly — use the modern ones in new code. The deprecated props still work for now but will be removed.

**Exception:** `SearchField` still uses `onChangeText` + `placeholderText` — those aren't deprecated there. `testID` is current everywhere.

## Theming

The codebase does **not** use styled-components. The convention is a `createStyles(colors)` factory that returns plain inline-style objects, consumed via `style={styles.foo}`.

**In the component** (reference: `src/pages/WelcomePage/CardCreateMasterPasswordV2/index.tsx`):

```tsx
import { useTheme } from '@tetherto/pearpass-lib-ui-kit'
import { createStyles } from './styles'

const Component = () => {
  const { theme } = useTheme()
  const styles = createStyles(theme.colors)
  return <div style={styles.card}>…</div>
}
```

**In the companion `styles.ts`** (reference: `src/pages/WelcomePage/CardCreateMasterPasswordV2/styles.ts`):

```ts
import type { ThemeColors } from '@tetherto/pearpass-lib-ui-kit'
import { rawTokens } from '@tetherto/pearpass-lib-ui-kit'

export const createStyles = (colors: ThemeColors) => ({
  card: {
    background: colors.colorSurfacePrimary,
    border: `1px solid ${colors.colorBorderPrimary}`,
    borderRadius: `${rawTokens.radius8}px`,
    padding: `${rawTokens.spacing24}px`,
    gap: `${rawTokens.spacing12}px`,
  },
})
```

### `rawTokens` — flat, numeric-suffixed keys (not nested)

- Spacing: `spacing2`, `spacing4`, `spacing6`, `spacing8`, `spacing10`, `spacing12`, `spacing16`, `spacing20`, `spacing24`, `spacing32`, `spacing40`, `spacing48` (all `number`, multiply with `${n}px`)
- Radius: `radius8`, `radius16`, `radius20`, `radius26`
- Font size: `fontSize12`, `fontSize14`, `fontSize16`, `fontSize24`, `fontSize28`
- Font family: `fontPrimary` (`"Inter"`), `fontDisplay` (`"Humble Nostalgia"`)
- Weight: `weightRegular` (`"400"`), `weightMedium` (`"500"`)

### `theme.colors` — common keys seen in this repo

`colorSurfacePrimary`, `colorSurfaceHover`, `colorBorderPrimary`, `colorBorderSecondary`, `colorTextPrimary`, `colorTextSecondary`, `colorTextTertiary`, `colorLinkText`. If you need one you haven't seen, inspect the `ThemeColors` type from `@tetherto/pearpass-lib-ui-kit`.

### When hardcoded values are OK

Tokens cover the design-system primitives. Feature-specific layout values (a card's `maxWidth: '500px'`, a one-off `padding: '55px 0'`) are fine as literals — these aren't design tokens. **Rule of thumb:** if the value corresponds to a semantic design decision (spacing step, brand color, radius), it must come from a token.

## Icons

```tsx
import { Add, Download, Folder, OpenInNew } from '@tetherto/pearpass-lib-ui-kit/icons'
```

530 icons, mostly Material Design, with style variants as suffixes: `Filled`, `Outlined`, `Round`, `Sharp`, `Tone` (e.g. `LockFilled`, `InfoOutlined`, `KeyboardArrowRightRound`). If a name has no suffix, it exists as a single variant.

**Commonly used in this repo** (check these first before browsing):

- **Actions:** `Add`, `Download`, `ContentCopy`, `Share`, `Send`, `Swap`, `UploadFileFilled`
- **Folder / organization:** `Folder`, `FolderOpen`, `FolderCopy`, `CreateNewFolder`, `Layers`
- **Navigation / arrows:** `KeyboardArrowRightFilled`, `KeyboardArrowRightRound`, `KeyboardArrowLeftFilled`, `ExpandMore`
- **Status / feedback:** `InfoOutlined`, `ReportProblemRound`, `ErrorFilled`, `Check`, `DoneAll`, `CheckBox`
- **Security:** `LockFilled`, `Key`, `SecurityFilled`, `Fingerprint`, `TwoFactorAuthenticationFilled`
- **External / misc:** `ImportOutlined`, `OpenInNew`

**Discovering others:** `ls node_modules/@tetherto/pearpass-lib-ui-kit/dist/icons/components/ | grep -i <keyword>` — names are PascalCase, grep is case-insensitive friendly.

## Anti-patterns to avoid

When editing a v2 file or creating new UI, do **not**:

- Add a new file under `src/lib-react-components/components/` for a Button/Input/Modal variant.
- Import `PearPassInputField`, `PearPassPasswordField`, or any `Button*` variant from `src/lib-react-components/` into any new file — swap to the kit equivalents.
- Add a `V2` suffix to a net-new file that has no v1 sibling. Suffix is only for migration coexistence.
- Use native `<button>`, `<input>`, or `<dialog>` in production code (tests are fine).
- Hardcode hex colors, brand radii, or design-system spacing — use `rawTokens` and `theme.colors`. (Feature-specific layout literals like `maxWidth: '500px'` are fine.)
- Add new SVG files under `src/` when the kit's icons subpath covers them.
- Introduce `styled-components` — the convention is `createStyles(colors)` returning plain style objects.

When editing a v1 file and you spot these patterns, mention them to the user but **don't do drive-by rewrites** unless asked — v1 migration is scoped work.

## When the kit truly lacks something

1. Confirm by grepping `node_modules/@tetherto/pearpass-lib-ui-kit/dist/components/` for the concept.
2. Check if a composition of existing kit primitives covers it (e.g. `Pressable` + `Text` + tokens).
3. If still missing, surface it to the user: "The kit doesn't export X — options are (a) compose from Y + Z, (b) request X be added upstream, (c) temporary local component. Which?" Do not silently create (c).


================================================
FILE: .cursor/rules/use-ui-kit.mdc
================================================
---
description: Rules for building and editing UI in this repo. The repo uses @tetherto/pearpass-lib-ui-kit as the single source for UI primitives — do not roll custom buttons, inputs, modals, typography, or icons. Covers component catalog, props (modern vs deprecated), theming conventions, a cookbook of common patterns, and anti-patterns to avoid. Mirror of .claude/skills/use-ui-kit/SKILL.md — keep in sync.
globs: src/**/*.tsx, src/**/*.jsx, src/**/*.ts
alwaysApply: false
---

# Using `@tetherto/pearpass-lib-ui-kit`

This is the design system for the pearpass desktop app. All UI primitives come from here.

## When these rules apply — `DESKTOP_DESIGN_VERSION === 2`

The active design is controlled by the `DESKTOP_DESIGN_VERSION` flag from `@tetherto/pearpass-lib-constants` (read in `electron/runtime-config.cjs` and exposed via `isV2()` in `src/utils/designVersion.js`).

**Currently `DESKTOP_DESIGN_VERSION === 2`**, so kit components are the right choice for all new UI work today. The app is mid-migration from v1 (custom components in `src/lib-react-components/`) to v2 (kit-based). If the flag is ever flipped back to `1`, v1 components remain in place and continue to work — do not delete them.

## File naming: when to use the `V2` suffix

The `V2` suffix is a **coexistence marker**, not a design marker. Use it only when a v1 sibling already exists:

- **A v1 file already exists** for this component/screen → create a new file with the `V2` suffix next to it (e.g. v1 `CreateVaultModalContent.jsx` → new `CreateVaultModalContentV2.tsx`). Both live in the tree during migration; the branching happens at the call site via `isV2()`.
- **No v1 equivalent exists** (net-new feature, net-new component) → create the file with its natural name, **no `V2` suffix**. The codebase is already v2 by default, so the suffix would just be noise.

Before creating a file, glob the directory for the base name without the suffix. If nothing comes up, skip the suffix.

## Golden rules

1. **Check the catalog below before creating any component.** If it exists in the kit, use it — never wrap or reimplement.
2. **All new UI goes through the kit.** Any new `.tsx`/`.jsx` file — suffixed or not — must import from `@tetherto/pearpass-lib-ui-kit`, not from `src/lib-react-components/`.
3. **Never add variants under `src/lib-react-components/components/`** (`ButtonThin`, `ButtonPrimary`, `ButtonRoundIcon`, `PearPassInputField`, etc.). That tree is legacy; the kit's `Button` takes variants.
4. **Style with tokens.** Use `useTheme()` + `rawTokens`. No hardcoded hex colors or px spacing.
5. **Icons come from the kit.** `@tetherto/pearpass-lib-ui-kit/icons` has 530 icons. Do not add new SVGs under `src/`.
6. **If the kit lacks something you need, stop and ask the user.** Don't silently roll a custom component.

## Component catalog (31 components)

Import pattern: `import { ComponentName } from '@tetherto/pearpass-lib-ui-kit'`

### Actions
- `Button` — all CTAs. Takes variants; use instead of `ButtonThin`, `ButtonPrimary`, `ButtonSecondary`, `ButtonRoundIcon`, `ButtonLittle`, `ButtonFilter`, `ButtonFolder`, `ButtonRadio`, `ButtonSingleInput`, `ButtonCreate`.
- `Pressable` — low-level pressable wrapper for custom interactive elements.
- `Link` — text links.

### Forms
- `Form` — form wrapper with validation.
- `InputField` — text input. Use instead of `PearPassInputField`.
- `PasswordField` — password input with strength indicator. Use instead of `PearPassPasswordField` / `PearPassPasswordFieldV2`.
- `SearchField` — search input.
- `SelectField` — dropdown select.
- `Dropdown` — low-level dropdown primitive.
- `TextArea` — multiline text input.
- `Checkbox`
- `Radio`
- `ToggleSwitch`
- `Slider`
- `DateField`
- `AttachmentField`
- `UploadField`
- `MultiSlotInput` — split inputs for OTP / recovery codes. Use instead of custom `OtpCodeField`.
- `FieldError` — inline field validation error.

### Typography
- `Title` — headings.
- `Text` — body text.

### Layout / surfaces
- `Dialog` — modals. Use instead of custom `ModalContent` wrappers.
- `NativeBottomSheet` — bottom sheets.
- `PageHeader` — top-of-page header.
- `ItemScreenHeader` — item-detail header.
- `Breadcrumb`
- `ListItem`
- `NavbarListItem`
- `ContextMenu`

### Feedback
- `AlertMessage` — inline alerts. Reference: `src/pages/WelcomePage/CardCreateMasterPasswordV2/index.tsx`.
- `Snackbar` — toast-style notifications.
- `PasswordIndicator` — standalone password strength meter.

### Type exports
- `ThemeColors`, `Theme`, `ThemeType`, `RawTokens`
- `PasswordIndicatorVariant` — `'vulnerable' | 'decent' | 'strong'`

Import types with `import type { ... } from '@tetherto/pearpass-lib-ui-kit'`.

## Component props (15 most-used)

Required props have no `?`. **Always include a test ID on interactive components** — see the "Test IDs" section below for which prop to use per component.

- **Button** — `variant: 'primary' | 'secondary' | 'tertiary' | 'destructive'`, `size: 'small' | 'medium'`, `onClick`, `children`, `type?: 'button' | 'submit'`, `disabled?`, `isLoading?`, `iconBefore?`, `iconAfter?`, `data-testid?`. Icon-only buttons need `aria-label`.
- **Dialog** — `title` (ReactNode), `onClose?`, `open?`, `footer?`, `children?`, `closeOnOutsideClick?`, `hideCloseButton?`, `trapFocus?`, `initialFocusRef?`, `testID?`, `closeButtonTestID?`. Put action buttons in `footer`.
- **InputField** — `label`, `value`, `onChange?: (e) => void`, `placeholder?`, `error?: string`, `inputType?: 'text' | 'password'`, `disabled?`, `readOnly?`, `copyable?`, `onCopy?`, `leftSlot?`, `rightSlot?`, `testID?`.
- **PasswordField** — `label`, `value`, `onChange?`, `placeholder?`, `error?`, `passwordIndicator?: 'vulnerable' | 'decent' | 'strong' | 'match'`, `infoBox?: string`, `copyable?`, `testID?`.
- **SearchField** — `value`, `onChangeText` (yes, this one is still current), `placeholderText?`, `size?: 'small' | 'medium'`, `testID?`.
- **Form** — `children`, `onSubmit?`, `noValidate?`, `testID?`. Wrap fields here; pair with `useForm` from `@tetherto/pear-apps-lib-ui-react-hooks`.
- **Text** — `children`, `as?: 'p' | 'span'`, `variant?: 'label' | 'labelEmphasized' | 'body' | 'bodyEmphasized' | 'caption'`, `color?`, `numberOfLines?`, `data-testid?`.
- **Title** — `children`, `as?: 'h1' | 'h2' | ... | 'h6'`, `data-testid?`.
- **AlertMessage** — `variant: 'info' | 'warning' | 'error'`, `size: 'small' | 'medium' | 'big'`, `title`, `description`, `actionText?`, `onAction?`, `testID?`, `actionTestId?`.
- **ToggleSwitch** — `checked?`, `onChange?: (b: boolean) => void`, `label?`, `description?`, `disabled?`, `data-testid?`.
- **Checkbox** — same shape as `ToggleSwitch` (uses `data-testid`).
- **Radio** — `options: Array<{value, label?, description?, disabled?}>`, `value?`, `onChange?: (v: string) => void`, `testID?`.
- **SelectField** — `label`, `value?`, `placeholder?`, `onClick?` (opens dropdown), `error?`, `disabled?`, `leftSlot?`, `rightSlot?`, `testID?`.
- **TextArea** — `value`, `onChange?`, `label?`, `placeholder?`, `error?`, `disabled?`, `testID?`.
- **Link** — `children`, `href?`, `isExternal?`, `onClick?`, `data-testid?` (and standard `<a>` attributes).

For components not listed, open `node_modules/@tetherto/pearpass-lib-ui-kit/dist/components/<Name>/types.d.ts`.

### Test IDs — `testID` vs `data-testid`

Always include a test ID on anything a user interacts with (buttons, fields, toggles, dialogs). Which prop depends on the component:

- **`testID`** — components that declare it explicitly: `Dialog` (+ `closeButtonTestID`), `Form`, `InputField`, `PasswordField`, `SearchField`, `SelectField`, `TextArea`, `Radio`, `AlertMessage` (+ `actionTestId`).
- **`data-testid`** — components that extend native HTML and don't redeclare it: `Button`, `ToggleSwitch`, `Checkbox`, `Link`, `Pressable`, `Text`, `Title`.

Rule of thumb: try `testID` first; if TypeScript rejects it, use `data-testid`. When editing an existing file, follow the naming pattern already there (e.g. `createvault-name-v2`, `createvault-discard-v2`).

### Prop naming — modern vs. deprecated (important)

The kit recently renamed several field props. **Use the modern names:**

| Use | Not (deprecated) |
| --- | --- |
| `onChange` (receives `ChangeEvent`) | `onChangeText` (receives string) |
| `placeholder` | `placeholderText` |
| `error` (string) | `errorMessage` + `variant` |

⚠️ **Existing v2 files in this repo (e.g. `CreateVaultModalContentV2`, `CardCreateMasterPasswordV2`) still use the deprecated props.** Don't copy their prop names blindly — use the modern ones in new code. The deprecated props still work for now but will be removed.

**Exception:** `SearchField` still uses `onChangeText` + `placeholderText` — those aren't deprecated there. `testID` is current everywhere.

## Theming

The codebase does **not** use styled-components. The convention is a `createStyles(colors)` factory that returns plain inline-style objects, consumed via `style={styles.foo}`.

**In the component** (reference: `src/pages/WelcomePage/CardCreateMasterPasswordV2/index.tsx`):

```tsx
import { useTheme } from '@tetherto/pearpass-lib-ui-kit'
import { createStyles } from './styles'

const Component = () => {
  const { theme } = useTheme()
  const styles = createStyles(theme.colors)
  return <div style={styles.card}>…</div>
}
```

**In the companion `styles.ts`** (reference: `src/pages/WelcomePage/CardCreateMasterPasswordV2/styles.ts`):

```ts
import type { ThemeColors } from '@tetherto/pearpass-lib-ui-kit'
import { rawTokens } from '@tetherto/pearpass-lib-ui-kit'

export const createStyles = (colors: ThemeColors) => ({
  card: {
    background: colors.colorSurfacePrimary,
    border: `1px solid ${colors.colorBorderPrimary}`,
    borderRadius: `${rawTokens.radius8}px`,
    padding: `${rawTokens.spacing24}px`,
    gap: `${rawTokens.spacing12}px`,
  },
})
```

### `rawTokens` — flat, numeric-suffixed keys (not nested)

- Spacing: `spacing2`, `spacing4`, `spacing6`, `spacing8`, `spacing10`, `spacing12`, `spacing16`, `spacing20`, `spacing24`, `spacing32`, `spacing40`, `spacing48` (all `number`, multiply with `${n}px`)
- Radius: `radius8`, `radius16`, `radius20`, `radius26`
- Font size: `fontSize12`, `fontSize14`, `fontSize16`, `fontSize24`, `fontSize28`
- Font family: `fontPrimary` (`"Inter"`), `fontDisplay` (`"Humble Nostalgia"`)
- Weight: `weightRegular` (`"400"`), `weightMedium` (`"500"`)

### `theme.colors` — common keys seen in this repo

`colorSurfacePrimary`, `colorSurfaceHover`, `colorBorderPrimary`, `colorBorderSecondary`, `colorTextPrimary`, `colorTextSecondary`, `colorTextTertiary`, `colorLinkText`. If you need one you haven't seen, inspect the `ThemeColors` type from `@tetherto/pearpass-lib-ui-kit`.

### When hardcoded values are OK

Tokens cover the design-system primitives. Feature-specific layout values (a card's `maxWidth: '500px'`, a one-off `padding: '55px 0'`) are fine as literals — these aren't design tokens. **Rule of thumb:** if the value corresponds to a semantic design decision (spacing step, brand color, radius), it must come from a token.

## Icons

```tsx
import { Add, Download, Folder, OpenInNew } from '@tetherto/pearpass-lib-ui-kit/icons'
```

530 icons, mostly Material Design, with style variants as suffixes: `Filled`, `Outlined`, `Round`, `Sharp`, `Tone` (e.g. `LockFilled`, `InfoOutlined`, `KeyboardArrowRightRound`). If a name has no suffix, it exists as a single variant.

**Commonly used in this repo** (check these first before browsing):

- **Actions:** `Add`, `Download`, `ContentCopy`, `Share`, `Send`, `Swap`, `UploadFileFilled`
- **Folder / organization:** `Folder`, `FolderOpen`, `FolderCopy`, `CreateNewFolder`, `Layers`
- **Navigation / arrows:** `KeyboardArrowRightFilled`, `KeyboardArrowRightRound`, `KeyboardArrowLeftFilled`, `ExpandMore`
- **Status / feedback:** `InfoOutlined`, `ReportProblemRound`, `ErrorFilled`, `Check`, `DoneAll`, `CheckBox`
- **Security:** `LockFilled`, `Key`, `SecurityFilled`, `Fingerprint`, `TwoFactorAuthenticationFilled`
- **External / misc:** `ImportOutlined`, `OpenInNew`

**Discovering others:** `ls node_modules/@tetherto/pearpass-lib-ui-kit/dist/icons/components/ | grep -i <keyword>` — names are PascalCase, grep is case-insensitive friendly.

## Anti-patterns to avoid

When editing a v2 file or creating new UI, do **not**:

- Add a new file under `src/lib-react-components/components/` for a Button/Input/Modal variant.
- Import `PearPassInputField`, `PearPassPasswordField`, or any `Button*` variant from `src/lib-react-components/` into any new file — swap to the kit equivalents.
- Add a `V2` suffix to a net-new file that has no v1 sibling. Suffix is only for migration coexistence.
- Use native `<button>`, `<input>`, or `<dialog>` in production code (tests are fine).
- Hardcode hex colors, brand radii, or design-system spacing — use `rawTokens` and `theme.colors`. (Feature-specific layout literals like `maxWidth: '500px'` are fine.)
- Add new SVG files under `src/` when the kit's icons subpath covers them.
- Introduce `styled-components` — the convention is `createStyles(colors)` returning plain style objects.

When editing a v1 file and you spot these patterns, mention them to the user but **don't do drive-by rewrites** unless asked — v1 migration is scoped work.

## When the kit truly lacks something

1. Confirm by grepping `node_modules/@tetherto/pearpass-lib-ui-kit/dist/components/` for the concept.
2. Check if a composition of existing kit primitives covers it (e.g. `Pressable` + `Text` + tokens).
3. If still missing, surface it to the user: "The kit doesn't export X — options are (a) compose from Y + Z, (b) request X be added upstream, (c) temporary local component. Which?" Do not silently create (c).


================================================
FILE: .github/actions/authorize-pr/action.yml
================================================
name: Authorize PR
description: >
  Security gate for pull_request_target workflows only. Checks actor
  write permission, author association, and label presence for external
  fork PRs. Strips the gate label on synchronize events from non-writers
  to force re-review after new commits.

inputs:
  label:
    description: "Label name required for external fork PRs (default: verify)"
    required: false
    default: "verify"
  github-token:
    description: "GitHub token for permission check and label API calls"
    required: true

outputs:
  allowed:
    description: "true when the workflow is authorized to proceed"
    value: ${{ steps.check.outputs.allowed }}

runs:
  using: composite
  steps:
    # -----------------------------------------------------------------
    # 1. Resolve actor permission level
    # -----------------------------------------------------------------
    - name: Check actor write permission
      id: perm
      uses: scherermichael-oss/action-has-permission@136e061bfe093832d87f090dd768e14e27a740d3 # 1.0.6
      with:
        required-permission: write
      env:
        GITHUB_TOKEN: ${{ inputs.github-token }}

    # -----------------------------------------------------------------
    # 2. Decide whether the run is authorized
    # -----------------------------------------------------------------
    - name: Check authorization
      id: check
      shell: bash
      env:
        EVENT: ${{ github.event_name }}
        ACTION: ${{ github.event.action }}
        HEAD_REPO: ${{ github.event.pull_request.head.repo.full_name }}
        BASE_REPO: ${{ github.repository }}
        AUTHOR_ASSOC: ${{ github.event.pull_request.author_association }}
        HAS_WRITE: ${{ steps.perm.outputs.has-permission }}
        LABEL_NAME: ${{ inputs.label }}
        LABELS_JSON: ${{ toJSON(github.event.pull_request.labels.*.name) }}
      run: |
        # Non-PR events (push, workflow_dispatch, workflow_call) are always trusted
        if [ "$EVENT" != "pull_request_target" ]; then
          echo "::notice::Non-PR event ($EVENT) — authorized"
          echo "allowed=true" >> "$GITHUB_OUTPUT"
          exit 0
        fi

        # Same-repo PRs are always trusted
        if [ "$HEAD_REPO" = "$BASE_REPO" ]; then
          echo "::notice::Same-repo PR — authorized"
          echo "allowed=true" >> "$GITHUB_OUTPUT"
          exit 0
        fi

        echo "::notice::Actor '$GITHUB_ACTOR' write-or-higher: $HAS_WRITE"

        # Actor with write (or higher) is trusted even from a fork
        if [ "$HAS_WRITE" = "1" ]; then
          echo "::notice::Actor has write-or-higher permission — authorized"
          echo "allowed=true" >> "$GITHUB_OUTPUT"
          exit 0
        fi

        # Org members / collaborators are trusted even from forks
        if [[ "$AUTHOR_ASSOC" =~ ^(MEMBER|OWNER|COLLABORATOR)$ ]]; then
          echo "::notice::Trusted author ($AUTHOR_ASSOC) — authorized"
          echo "allowed=true" >> "$GITHUB_OUTPUT"
          exit 0
        fi

        # External fork: require the gate label
        HAS_LABEL=$(echo "$LABELS_JSON" | jq -r --arg l "$LABEL_NAME" \
          'if . == null then "false" elif any(. == $l) then "true" else "false" end')

        # On synchronize from a non-writer, reject even if the label is still
        # present. The label was granted for the previous commit; new unreviewed
        # code must not run with secrets. The strip step below removes the label
        # so that a re-review + re-label is required for the next run.
        if [ "$ACTION" = "synchronize" ]; then
          echo "::warning::External fork synchronize from non-writer — blocking until re-review"
          echo "allowed=false" >> "$GITHUB_OUTPUT"
          exit 0
        fi

        if [ "$HAS_LABEL" = "true" ]; then
          echo "::notice::Fork PR with '$LABEL_NAME' label — authorized"
          echo "allowed=true" >> "$GITHUB_OUTPUT"
        else
          echo "::warning::External fork PR without '$LABEL_NAME' label — skipping"
          echo "::warning::A team member must review the code and apply the '$LABEL_NAME' label"
          echo "allowed=false" >> "$GITHUB_OUTPUT"
        fi

    # -----------------------------------------------------------------
    # 3. Strip the gate label on synchronize from non-writers
    #    This forces a maintainer to re-review before the NEXT run.
    # -----------------------------------------------------------------
    - name: Strip label on new pushes from non-writers
      if: |
        steps.perm.outputs.has-permission != '1' &&
        github.event.action == 'synchronize' &&
        github.event.pull_request.head.repo.full_name != github.repository
      shell: bash
      env:
        GH_TOKEN: ${{ inputs.github-token }}
        PR_NUMBER: ${{ github.event.pull_request.number }}
        LABEL: ${{ inputs.label }}
      run: |
        gh api -X DELETE \
          "repos/${{ github.repository }}/issues/${PR_NUMBER}/labels/${LABEL}" \
          2>/dev/null || true
        echo "::warning::Removed '${LABEL}' label — new commits from non-writer. Re-review required."


================================================
FILE: .github/pull_request_template.md
================================================
### Requirements
<!-- List the requirements for this PR -->

### Changes
<!-- Summarize the changes introduced in this PR -->

### Testing Notes
<!-- How did you test it? -->

### Things reviewers should pay attention to
<!-- Highlight anything specific you want reviewers to focus on -->

### Screenshots/Recordings
<!-- Add screenshots or screen recordings if applicable -->

### dependencies
<!-- List any dependent work items or PRs -->


================================================
FILE: .github/workflows/ci-pr.yml
================================================
name: PR CI

on:
  pull_request_target:
    types: [opened, reopened, synchronize, labeled]
    branches: [main]

permissions:
  contents: read

concurrency:
  group: pr-ci-${{ github.event.pull_request.number }}
  cancel-in-progress: true

jobs:
  authorize:
    if: github.event.action != 'labeled' || github.event.label.name == 'verify'
    runs-on: ubuntu-latest
    permissions:
      contents: read
      pull-requests: write
      issues: write
    outputs:
      allowed: ${{ steps.auth.outputs.allowed }}

    steps:
      - name: Checkout workflow code
        uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1

      - name: Authorize PR execution
        id: auth
        uses: ./.github/actions/authorize-pr
        with:
          github-token: ${{ github.token }}

      - name: Summarize skipped run
        if: steps.auth.outputs.allowed != 'true'
        run: |
          echo "PR CI is limited to members, collaborators, or external PRs labeled 'verify' (re-review required after each push)." >> "$GITHUB_STEP_SUMMARY"

  ci:
    needs: authorize
    if: needs.authorize.outputs.allowed == 'true'
    uses: ./.github/workflows/ci-reusable.yml
    with:
      checkout_ref: ${{ github.event.pull_request.head.sha }}
      checkout_repository: ${{ github.event.pull_request.head.repo.full_name }}


================================================
FILE: .github/workflows/ci-push.yml
================================================
name: Push CI

on:
  push:
    branches: [main]

permissions:
  contents: read

concurrency:
  group: push-ci-${{ github.ref }}
  cancel-in-progress: true

jobs:
  ci:
    uses: ./.github/workflows/ci-reusable.yml
    with:
      checkout_ref: ${{ github.sha }}
      checkout_repository: ${{ github.repository }}


================================================
FILE: .github/workflows/ci-reusable.yml
================================================
name: Reusable CI

on:
  workflow_call:
    inputs:
      checkout_ref:
        description: Git ref or SHA to validate
        required: true
        type: string
      checkout_repository:
        description: Repository that contains the ref to validate
        required: true
        type: string

permissions:
  contents: read

jobs:
  quality:
    runs-on: ubuntu-latest

    steps:
      - name: Checkout code
        uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
        with:
          ref: ${{ inputs.checkout_ref }}
          repository: ${{ inputs.checkout_repository }}
          persist-credentials: false

      - name: Setup Node
        uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
        with:
          node-version-file: .nvmrc
          cache: npm

      - name: Install dependencies
        run: npm ci --legacy-peer-deps

      - name: Compile locale files
        run: |
          npm run lingui:extract
          npm run lingui:compile

      - name: Run ESLint
        run: npm run lint

      - name: Run TypeScript typecheck
        run: npx tsc --noEmit

      - name: Run tests
        run: npm test


================================================
FILE: .gitignore
================================================
node_modules

src/locales

.yalc
yalc.lock
pass

#ignore IntelliJ or other Jetbrains files
.idea

# Generated log files
logs/

.env

.DS_Store
assets/.DS_Store

dist

# Build output
build/
*.AppImage
*.flatpak
*.snap

# Snapcraft build artifacts
.snapcraft.yaml.swp

# Flatpak build artifacts
flatpak/.flatpak-builder/
flatpak/build-dir/
flatpak/repo/
flatpak/icons/

# Editor swap files
*.swp
*.swo
*~

# VSCode
.vscode/

# Test coverage
coverage/

# TypeScript build info
*.tsbuildinfo

# bin.tar.gz generated by bare-build
*.bin.tar.gz
# Snapcraft
.snapcraft.yaml.swp

# Flatpak
flatpak/*.flatpak
flatpak/*.tar.gz
flatpak/build-dir/
flatpak/repo/
flatpak/.flatpak-builder/
.flatpak-builder/

# Appling (AppImage builds)
appling/*.AppImage
appling/node_modules/

# test files
test-results

out/

src/PearPass

================================================
FILE: .husky/pre-commit
================================================
npm run lint
npm run build:app


================================================
FILE: .npmrc
================================================
foreground-scripts=true
legacy-peer-deps=true


================================================
FILE: .nvmrc
================================================
22.12.0

================================================
FILE: AGENTS.md
================================================
# UI conventions for pearpass-app-desktop-tether

This is the Electron desktop app for PearPass. It's written in React + TypeScript. UI is built on the shared component library `@tetherto/pearpass-lib-ui-kit`.

This document is for **anyone contributing UI** to the repo — new hires, current engineers, and AI coding assistants (Claude Code, Cursor, Codex, etc.). It captures the component catalog, styling conventions, file-naming rules, and patterns we use when building UI in this app. Read it once before your first UI change; keep it open when you're in doubt.

## Design-system state — `DESKTOP_DESIGN_VERSION === 2`

Which design renders at runtime is controlled by the `DESKTOP_DESIGN_VERSION` flag from `@tetherto/pearpass-lib-constants` (resolved in [electron/runtime-config.cjs](electron/runtime-config.cjs) and exposed via `isV2()` in [src/utils/designVersion.js](src/utils/designVersion.js)).

**Currently `DESKTOP_DESIGN_VERSION === 2`**, so kit components are the right choice for all new UI work. Legacy v1 components still live under [src/lib-react-components/](src/lib-react-components/) and are rendered whenever `isV2()` returns `false` — do not delete them as part of v2 work.

## File naming: when to use the `V2` suffix

The `V2` suffix is a **coexistence marker**, not a design marker. Use it only when a v1 sibling already exists:

- **A v1 file already exists** for this component/screen → create a new file with the `V2` suffix next to it (e.g. v1 `CreateVaultModalContent.jsx` → new `CreateVaultModalContentV2.tsx`). Both live in the tree during migration; the branching happens at the call site via `isV2()`.
- **No v1 equivalent exists** (net-new feature, net-new component) → create the file with its natural name, **no `V2` suffix**. The codebase is already v2 by default, so the suffix would just be noise.

Before creating a file, glob the directory for the base name without the suffix. If nothing comes up, skip the suffix.

## Golden rules

1. **Check the catalog below before creating any component.** If it exists in the kit, use it — never wrap or reimplement.
2. **All new UI goes through the kit.** Any new `.tsx`/`.jsx` file — suffixed or not — must import from `@tetherto/pearpass-lib-ui-kit`, not from [src/lib-react-components/](src/lib-react-components/).
3. **Never add variants under [src/lib-react-components/components/](src/lib-react-components/components/)** (`ButtonThin`, `ButtonPrimary`, `ButtonRoundIcon`, `PearPassInputField`, etc.). That tree is legacy; the kit's `Button` takes variants.
4. **Style with tokens.** Use `useTheme()` + `rawTokens`. No hardcoded hex colors or px spacing.
5. **Icons come from the kit.** `@tetherto/pearpass-lib-ui-kit/icons` has 530 icons. Do not add new SVGs under `src/`.
6. **If the kit lacks something you need, stop and ask the user.** Don't silently roll a custom component.

## Component catalog (31 components)

Import pattern: `import { ComponentName } from '@tetherto/pearpass-lib-ui-kit'`

### Actions
- `Button` — all CTAs. Takes variants; use instead of `ButtonThin`, `ButtonPrimary`, `ButtonSecondary`, `ButtonRoundIcon`, `ButtonLittle`, `ButtonFilter`, `ButtonFolder`, `ButtonRadio`, `ButtonSingleInput`, `ButtonCreate`.
- `Pressable` — low-level pressable wrapper for custom interactive elements.
- `Link` — text links.

### Forms
- `Form` — form wrapper with validation.
- `InputField` — text input. Use instead of `PearPassInputField`.
- `PasswordField` — password input with strength indicator. Use instead of `PearPassPasswordField` / `PearPassPasswordFieldV2`.
- `SearchField` — search input.
- `SelectField` — dropdown select.
- `Dropdown` — low-level dropdown primitive.
- `TextArea` — multiline text input.
- `Checkbox`
- `Radio`
- `ToggleSwitch`
- `Slider`
- `DateField`
- `AttachmentField`
- `UploadField`
- `MultiSlotInput` — split inputs for OTP / recovery codes. Use instead of custom `OtpCodeField`.
- `FieldError` — inline field validation error.

### Typography
- `Title` — headings.
- `Text` — body text.

### Layout / surfaces
- `Dialog` — modals. Use instead of custom `ModalContent` wrappers.
- `NativeBottomSheet` — bottom sheets.
- `PageHeader` — top-of-page header.
- `ItemScreenHeader` — item-detail header.
- `Breadcrumb`
- `ListItem`
- `NavbarListItem`
- `ContextMenu`

### Feedback
- `AlertMessage` — inline alerts. Reference: [src/pages/WelcomePage/CardCreateMasterPasswordV2/index.tsx](src/pages/WelcomePage/CardCreateMasterPasswordV2/index.tsx).
- `Snackbar` — toast-style notifications.
- `PasswordIndicator` — standalone password strength meter.

### Type exports
- `ThemeColors`, `Theme`, `ThemeType`, `RawTokens`
- `PasswordIndicatorVariant` — `'vulnerable' | 'decent' | 'strong'`

Import types with `import type { ... } from '@tetherto/pearpass-lib-ui-kit'`.

## Component props (15 most-used)

Required props have no `?`. **Always include a test ID on interactive components** — see the "Test IDs" section below for which prop to use per component.

- **Button** — `variant: 'primary' | 'secondary' | 'tertiary' | 'destructive'`, `size: 'small' | 'medium'`, `onClick`, `children`, `type?: 'button' | 'submit'`, `disabled?`, `isLoading?`, `iconBefore?`, `iconAfter?`, `data-testid?`. Icon-only buttons need `aria-label`.
- **Dialog** — `title` (ReactNode), `onClose?`, `open?`, `footer?`, `children?`, `closeOnOutsideClick?`, `hideCloseButton?`, `trapFocus?`, `initialFocusRef?`, `testID?`, `closeButtonTestID?`. Put action buttons in `footer`.
- **InputField** — `label`, `value`, `onChange?: (e) => void`, `placeholder?`, `error?: string`, `inputType?: 'text' | 'password'`, `disabled?`, `readOnly?`, `copyable?`, `onCopy?`, `leftSlot?`, `rightSlot?`, `testID?`.
- **PasswordField** — `label`, `value`, `onChange?`, `placeholder?`, `error?`, `passwordIndicator?: 'vulnerable' | 'decent' | 'strong' | 'match'`, `infoBox?: string`, `copyable?`, `testID?`.
- **SearchField** — `value`, `onChangeText` (yes, this one is still current), `placeholderText?`, `size?: 'small' | 'medium'`, `testID?`.
- **Form** — `children`, `onSubmit?`, `noValidate?`, `testID?`. Wrap fields here; pair with `useForm` from `@tetherto/pear-apps-lib-ui-react-hooks`.
- **Text** — `children`, `as?: 'p' | 'span'`, `variant?: 'label' | 'labelEmphasized' | 'body' | 'bodyEmphasized' | 'caption'`, `color?`, `numberOfLines?`, `data-testid?`.
- **Title** — `children`, `as?: 'h1' | 'h2' | ... | 'h6'`, `data-testid?`.
- **AlertMessage** — `variant: 'info' | 'warning' | 'error'`, `size: 'small' | 'medium' | 'big'`, `title`, `description`, `actionText?`, `onAction?`, `testID?`, `actionTestId?`.
- **ToggleSwitch** — `checked?`, `onChange?: (b: boolean) => void`, `label?`, `description?`, `disabled?`, `data-testid?`.
- **Checkbox** — same shape as `ToggleSwitch` (uses `data-testid`).
- **Radio** — `options: Array<{value, label?, description?, disabled?}>`, `value?`, `onChange?: (v: string) => void`, `testID?`.
- **SelectField** — `label`, `value?`, `placeholder?`, `onClick?` (opens dropdown), `error?`, `disabled?`, `leftSlot?`, `rightSlot?`, `testID?`.
- **TextArea** — `value`, `onChange?`, `label?`, `placeholder?`, `error?`, `disabled?`, `testID?`.
- **Link** — `children`, `href?`, `isExternal?`, `onClick?`, `data-testid?` (and standard `<a>` attributes).

For components not listed, open `node_modules/@tetherto/pearpass-lib-ui-kit/dist/components/<Name>/types.d.ts`.

### Test IDs — `testID` vs `data-testid`

Always include a test ID on anything a user interacts with (buttons, fields, toggles, dialogs). Which prop depends on the component:

- **`testID`** — components that declare it explicitly: `Dialog` (+ `closeButtonTestID`), `Form`, `InputField`, `PasswordField`, `SearchField`, `SelectField`, `TextArea`, `Radio`, `AlertMessage` (+ `actionTestId`).
- **`data-testid`** — components that extend native HTML and don't redeclare it: `Button`, `ToggleSwitch`, `Checkbox`, `Link`, `Pressable`, `Text`, `Title`.

Rule of thumb: try `testID` first; if TypeScript rejects it, use `data-testid`. When editing an existing file, follow the naming pattern already there (e.g. `createvault-name-v2`, `createvault-discard-v2`).

### Prop naming — modern vs. deprecated (important)

The kit recently renamed several field props. **Use the modern names:**

| Use | Not (deprecated) |
| --- | --- |
| `onChange` (receives `ChangeEvent`) | `onChangeText` (receives string) |
| `placeholder` | `placeholderText` |
| `error` (string) | `errorMessage` + `variant` |

⚠️ **Existing v2 files in this repo (e.g. `CreateVaultModalContentV2`, `CardCreateMasterPasswordV2`) still use the deprecated props.** Don't copy their prop names blindly — use the modern ones in new code. The deprecated props still work for now but will be removed.

**Exception:** `SearchField` still uses `onChangeText` + `placeholderText` — those aren't deprecated there. `testID` is current everywhere.

## Theming

The codebase does **not** use styled-components. The convention is a `createStyles(colors)` factory that returns plain inline-style objects, consumed via `style={styles.foo}`.

**In the component** ([reference](src/pages/WelcomePage/CardCreateMasterPasswordV2/index.tsx)):

```tsx
import { useTheme } from '@tetherto/pearpass-lib-ui-kit'
import { createStyles } from './styles'

const Component = () => {
  const { theme } = useTheme()
  const styles = createStyles(theme.colors)
  return <div style={styles.card}>…</div>
}
```

**In the companion `styles.ts`** ([reference](src/pages/WelcomePage/CardCreateMasterPasswordV2/styles.ts)):

```ts
import type { ThemeColors } from '@tetherto/pearpass-lib-ui-kit'
import { rawTokens } from '@tetherto/pearpass-lib-ui-kit'

export const createStyles = (colors: ThemeColors) => ({
  card: {
    background: colors.colorSurfacePrimary,
    border: `1px solid ${colors.colorBorderPrimary}`,
    borderRadius: `${rawTokens.radius8}px`,
    padding: `${rawTokens.spacing24}px`,
    gap: `${rawTokens.spacing12}px`,
  },
})
```

### `rawTokens` — flat, numeric-suffixed keys (not nested)

- Spacing: `spacing2`, `spacing4`, `spacing6`, `spacing8`, `spacing10`, `spacing12`, `spacing16`, `spacing20`, `spacing24`, `spacing32`, `spacing40`, `spacing48` (all `number`, multiply with `${n}px`)
- Radius: `radius8`, `radius16`, `radius20`, `radius26`
- Font size: `fontSize12`, `fontSize14`, `fontSize16`, `fontSize24`, `fontSize28`
- Font family: `fontPrimary` (`"Inter"`), `fontDisplay` (`"Humble Nostalgia"`)
- Weight: `weightRegular` (`"400"`), `weightMedium` (`"500"`)

### `theme.colors` — common keys seen in this repo

`colorSurfacePrimary`, `colorSurfaceHover`, `colorBorderPrimary`, `colorBorderSecondary`, `colorTextPrimary`, `colorTextSecondary`, `colorTextTertiary`, `colorLinkText`. If you need one you haven't seen, inspect the `ThemeColors` type from `@tetherto/pearpass-lib-ui-kit`.

### When hardcoded values are OK

Tokens cover the design-system primitives. Feature-specific layout values (a card's `maxWidth: '500px'`, a one-off `padding: '55px 0'`) are fine as literals — these aren't design tokens. **Rule of thumb:** if the value corresponds to a semantic design decision (spacing step, brand color, radius), it must come from a token.

## Icons

```tsx
import { Add, Download, Folder, OpenInNew } from '@tetherto/pearpass-lib-ui-kit/icons'
```

530 icons, mostly Material Design, with style variants as suffixes: `Filled`, `Outlined`, `Round`, `Sharp`, `Tone` (e.g. `LockFilled`, `InfoOutlined`, `KeyboardArrowRightRound`). If a name has no suffix, it exists as a single variant.

**Commonly used in this repo** (check these first before browsing):

- **Actions:** `Add`, `Download`, `ContentCopy`, `Share`, `Send`, `Swap`, `UploadFileFilled`
- **Folder / organization:** `Folder`, `FolderOpen`, `FolderCopy`, `CreateNewFolder`, `Layers`
- **Navigation / arrows:** `KeyboardArrowRightFilled`, `KeyboardArrowRightRound`, `KeyboardArrowLeftFilled`, `ExpandMore`
- **Status / feedback:** `InfoOutlined`, `ReportProblemRound`, `ErrorFilled`, `Check`, `DoneAll`, `CheckBox`
- **Security:** `LockFilled`, `Key`, `SecurityFilled`, `Fingerprint`, `TwoFactorAuthenticationFilled`
- **External / misc:** `ImportOutlined`, `OpenInNew`

**Discovering others:** `ls node_modules/@tetherto/pearpass-lib-ui-kit/dist/icons/components/ | grep -i <keyword>` — names are PascalCase, grep is case-insensitive friendly.

## Anti-patterns to avoid

When editing a v2 file or creating new UI, do **not**:

- Add a new file under [src/lib-react-components/components/](src/lib-react-components/components/) for a Button/Input/Modal variant.
- Import `PearPassInputField`, `PearPassPasswordField`, or any `Button*` variant from `src/lib-react-components/` into any new file — swap to the kit equivalents.
- Add a `V2` suffix to a net-new file that has no v1 sibling. Suffix is only for migration coexistence.
- Use native `<button>`, `<input>`, or `<dialog>` in production code (tests are fine).
- Hardcode hex colors, brand radii, or design-system spacing — use `rawTokens` and `theme.colors`. (Feature-specific layout literals like `maxWidth: '500px'` are fine.)
- Add new SVG files under `src/` when the kit's icons subpath covers them.
- Introduce `styled-components` — the convention is `createStyles(colors)` returning plain style objects.

When editing a v1 file and you spot these patterns, mention them to the user but **don't do drive-by rewrites** unless asked — v1 migration is scoped work.

## When the kit truly lacks something

1. Confirm by grepping `node_modules/@tetherto/pearpass-lib-ui-kit/dist/components/` for the concept.
2. Check if a composition of existing kit primitives covers it (e.g. `Pressable` + `Text` + tokens).
3. If still missing, surface it to the user: "The kit doesn't export X — options are (a) compose from Y + Z, (b) request X be added upstream, (c) temporary local component. Which?" Do not silently create (c).


================================================
FILE: CLAUDE.md
================================================
# pearpass-app-desktop-tether

## UI: always use `@tetherto/pearpass-lib-ui-kit`

UI is built on `@tetherto/pearpass-lib-ui-kit`. All new UI — v2 redesigns of existing screens and net-new features — **must** use components from this kit. Do not roll custom buttons, inputs, modals, typography, or icons.

**Full guide:** the component catalog, props, styling conventions, and reference files live in [AGENTS.md](AGENTS.md) (canonical contributor doc, also loaded by Codex/Cursor). Claude Code's skill trigger loads the same content via [.claude/skills/use-ui-kit/SKILL.md](.claude/skills/use-ui-kit/SKILL.md) whenever you're editing `.tsx`/`.jsx` files.

**Hard rules:**
- If a component exists in the kit, use it. If it does not, raise it with the team before creating a local alternative.
- Do **not** add new files under [src/lib-react-components/components/](src/lib-react-components/components/) — that tree is legacy (v1) and should not grow.
- Style with `useTheme()` + `rawTokens` from the kit. No hardcoded hex colors or design-system spacing.
- Import icons from `@tetherto/pearpass-lib-ui-kit/icons` (530 available). Do not add new SVGs under `src/`.

**When the kit applies:** the active design is controlled by the `DESKTOP_DESIGN_VERSION` flag from `@tetherto/pearpass-lib-constants`. Currently `DESKTOP_DESIGN_VERSION === 2`, so kit components are the default for all new UI.

**`V2` suffix is for coexistence only.** If a v1 file already exists for the component you're creating, add the `V2` suffix (e.g. `CardCreateMasterPasswordV2` alongside legacy `CardCreateMasterPassword`). If the component is net-new with no v1 sibling, use its natural name — **no suffix**.

## React: `useEffect` dependency safety

Before adding a function to a `useEffect` dependency array, verify it is stable (memoized with `useCallback`, a `useRef`-held ref, or a module-level constant). Un-memoized functions are recreated on every render and will cause the effect to fire on every render, producing an infinite loop. If the function is not memoized: wrap it in `useCallback` if it lives in this repository, or omit it from the dependency array if it comes from an external package/repository (where you cannot control memoization).


================================================
FILE: CONTRIBUTING.md
================================================
# Contributing to PearPass

The source is open to use as per the [LICENSE](./LICENSE).
 
Be aware that **any pull-request or issue may be closed without explanation**.

## Issues

- Feature requests are welcome. 
- For bug reports, please provide a failing test case or steps to reproduce. 

## Pull Request

Newer contributors are encouraged to start small and simple. Tests - both failing and passing - are very helpful.

- Keep pull requests focused on a single feature or bug fix
- Provide a clear description of changes
- Ensure code passes linting: `npm run lint`
- Ensure tests are passing: `npm test`
  - except, of course, any added failing tests


## License

By contributing, you agree that your contributions will be licensed under the project [LICENSE](./LICENSE).


================================================
FILE: LICENSE.md
================================================
# License

This project is licensed under the Apache License, Version 2.0.
See below for the full license text.

## Apache License

_Version 2.0, January 2004_  
_&lt;<http://www.apache.org/licenses/>&gt;_

### Terms and Conditions for use, reproduction, and distribution

#### 1. Definitions

“License” shall mean the terms and conditions for use, reproduction, and
distribution as defined by Sections 1 through 9 of this document.

“Licensor” shall mean the copyright owner or entity authorized by the copyright
owner that is granting the License.

“Legal Entity” shall mean the union of the acting entity and all other entities
that control, are controlled by, or are under common control with that entity.
For the purposes of this definition, “control” means **(i)** the power, direct or
indirect, to cause the direction or management of such entity, whether by
contract or otherwise, or **(ii)** ownership of fifty percent (50%) or more of the
outstanding shares, or **(iii)** beneficial ownership of such entity.

“You” (or “Your”) shall mean an individual or Legal Entity exercising
permissions granted by this License.

“Source” form shall mean the preferred form for making modifications, including
but not limited to software source code, documentation source, and configuration
files.

“Object” form shall mean any form resulting from mechanical transformation or
translation of a Source form, including but not limited to compiled object code,
generated documentation, and conversions to other media types.

“Work” shall mean the work of authorship, whether in Source or Object form, made
available under the License, as indicated by a copyright notice that is included
in or attached to the work (an example is provided in the Appendix below).

“Derivative Works” shall mean any work, whether in Source or Object form, that
is based on (or derived from) the Work and for which the editorial revisions,
annotations, elaborations, or other modifications represent, as a whole, an
original work of authorship. For the purposes of this License, Derivative Works
shall not include works that remain separable from, or merely link (or bind by
name) to the interfaces of, the Work and Derivative Works thereof.

“Contribution” shall mean any work of authorship, including the original version
of the Work and any modifications or additions to that Work or Derivative Works
thereof, that is intentionally submitted to Licensor for inclusion in the Work
by the copyright owner or by an individual or Legal Entity authorized to submit
on behalf of the copyright owner. For the purposes of this definition,
“submitted” means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems, and
issue tracking systems that are managed by, or on behalf of, the Licensor for
the purpose of discussing and improving the Work, but excluding communication
that is conspicuously marked or otherwise designated in writing by the copyright
owner as “Not a Contribution.”

“Contributor” shall mean Licensor and any individual or Legal Entity on behalf
of whom a Contribution has been received by Licensor and subsequently
incorporated within the Work.

#### 2. Grant of Copyright License

Subject to the terms and conditions of this License, each Contributor hereby
grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,
irrevocable copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the Work and such
Derivative Works in Source or Object form.

#### 3. Grant of Patent License

Subject to the terms and conditions of this License, each Contributor hereby
grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,
irrevocable (except as stated in this section) patent license to make, have
made, use, offer to sell, sell, import, and otherwise transfer the Work, where
such license applies only to those patent claims licensable by such Contributor
that are necessarily infringed by their Contribution(s) alone or by combination
of their Contribution(s) with the Work to which such Contribution(s) was
submitted. If You institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work or a
Contribution incorporated within the Work constitutes direct or contributory
patent infringement, then any patent licenses granted to You under this License
for that Work shall terminate as of the date such litigation is filed.

#### 4. Redistribution

You may reproduce and distribute copies of the Work or Derivative Works thereof
in any medium, with or without modifications, and in Source or Object form,
provided that You meet the following conditions:

* **(a)** You must give any other recipients of the Work or Derivative Works a copy of
this License; and
* **(b)** You must cause any modified files to carry prominent notices stating that You
changed the files; and
* **(c)** You must retain, in the Source form of any Derivative Works that You distribute,
all copyright, patent, trademark, and attribution notices from the Source form
of the Work, excluding those notices that do not pertain to any part of the
Derivative Works; and
* **(d)** If the Work includes a “NOTICE” text file as part of its distribution, then any
Derivative Works that You distribute must include a readable copy of the
attribution notices contained within such NOTICE file, excluding those notices
that do not pertain to any part of the Derivative Works, in at least one of the
following places: within a NOTICE text file distributed as part of the
Derivative Works; within the Source form or documentation, if provided along
with the Derivative Works; or, within a display generated by the Derivative
Works, if and wherever such third-party notices normally appear. The contents of
the NOTICE file are for informational purposes only and do not modify the
License. You may add Your own attribution notices within Derivative Works that
You distribute, alongside or as an addendum to the NOTICE text from the Work,
provided that such additional attribution notices cannot be construed as
modifying the License.

You may add Your own copyright statement to Your modifications and may provide
additional or different license terms and conditions for use, reproduction, or
distribution of Your modifications, or for any such Derivative Works as a whole,
provided Your use, reproduction, and distribution of the Work otherwise complies
with the conditions stated in this License.

#### 5. Submission of Contributions

Unless You explicitly state otherwise, any Contribution intentionally submitted
for inclusion in the Work by You to the Licensor shall be under the terms and
conditions of this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify the terms of
any separate license agreement you may have executed with Licensor regarding
such Contributions.

#### 6. Trademarks

This License does not grant permission to use the trade names, trademarks,
service marks, or product names of the Licensor, except as required for
reasonable and customary use in describing the origin of the Work and
reproducing the content of the NOTICE file.

#### 7. Disclaimer of Warranty

Unless required by applicable law or agreed to in writing, Licensor provides the
Work (and each Contributor provides its Contributions) on an “AS IS” BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied,
including, without limitation, any warranties or conditions of TITLE,
NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are
solely responsible for determining the appropriateness of using or
redistributing the Work and assume any risks associated with Your exercise of
permissions under this License.

#### 8. Limitation of Liability

In no event and under no legal theory, whether in tort (including negligence),
contract, or otherwise, unless required by applicable law (such as deliberate
and grossly negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special, incidental,
or consequential damages of any character arising as a result of this License or
out of the use or inability to use the Work (including but not limited to
damages for loss of goodwill, work stoppage, computer failure or malfunction, or
any and all other commercial damages or losses), even if such Contributor has
been advised of the possibility of such damages.

#### 9. Accepting Warranty or Additional Liability

While redistributing the Work or Derivative Works thereof, You may choose to
offer, and charge a fee for, acceptance of support, warranty, indemnity, or
other liability obligations and/or rights consistent with this License. However,
in accepting such obligations, You may act only on Your own behalf and on Your
sole responsibility, not on behalf of any other Contributor, and only if You
agree to indemnify, defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason of your
accepting any such warranty or additional liability.

_END OF TERMS AND CONDITIONS_

### APPENDIX: How to apply the Apache License to your work

To apply the Apache License to your work, attach the following boilerplate
notice, with the fields enclosed by brackets `[]` replaced with your own
identifying information. (Don't include the brackets!) The text should be
enclosed in the appropriate comment syntax for the file format. We also
recommend that a file or class name and description of purpose be included on
the same “printed page” as the copyright notice for easier identification within
third-party archives.

Copyright 2026 Tether Inc

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

================================================
FILE: NOTICE.md
================================================
Copyright 2026 Tether Inc

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

================================================
FILE: README.md
================================================
<p align="center">
  <img src="assets/images/logo.png" alt="Pearpass logo" width="264"/>
</p>

# PearPass Desktop

> The desktop app for PearPass, an open-source, end-to-end encrypted password and identity manager built on Pear Runtime.

---

## Table of Contents

- [Introduction](#introduction)
- [Features](#features)
- [Installation](#installation)
- [Usage Examples](#usage-examples)
- [Logging](#logging)
- [Testing](#testing)
- [Staging to Dev](#staging-to-dev)
- [Workspace Dependencies](#workspace-dependencies)
- [Dependencies](#dependencies)
- [Related Projects](#related-projects)
- [Contributing](#contributing)
- [License](#license)

---

## Introduction

PearPass is an open-source, privacy-first password and identity manager that gives you full control over your sensitive information. It makes storing and managing your credentials simple, secure, and private. PearPass encrypts and stores all data locally on your device.

Unlike traditional password managers that rely on centralized servers, PearPass is built on [Pear Runtime](https://pears.com/) and uses peer-to-peer technology to sync your credentials directly between your devices, ensuring they remain private, secure, and always under your control.

---

## Features

- **Encrypted-at-rest storage** — PearPass encrypts passwords, credit cards, secure notes, and custom fields before writing them to disk.
- **Cross-device sync** — PearPass syncs credentials directly between your devices using Pear Runtime, with no central server.
- **Offline access** — Access your vault anytime, even without a network connection.
- **Password health** — Analyse password strength and identify weak passwords.
- **Random password generator** — Generate strong, unique passwords.
- **Multi-platform** — Runs on macOS, Linux, and Windows. PearPass is also available on [mobile](https://github.com/tetherto/pearpass-app-mobile) and as a [browser extension](https://github.com/tetherto/pearpass-app-browser-extension).

---

## Installation

### Prerequisites

- **Node.js** — check the required version in `.nvmrc` and verify with:

```bash
node --version
```

- **Pear Runtime** — [Installation guide](https://docs.pears.com/guide/getting-started.html).

### Steps

```bash
# 1. Clone the repository
git clone git@github.com:tetherto/pearpass-app-desktop.git

# 2. Go to the cloned directory
cd pearpass-app-desktop

# 3. Install dependencies
npm install

# 4. Generate translation keys
npm run build

# 5. Start the development app
npm run dev
```

---

## Usage Examples

Visit the official PearPass documentation for step-by-step guides on setup, vault management, syncing across devices, browser extension usage, and all other PearPass features:

**[docs.pass.pears.com](https://docs.pass.pears.com)**

> ⚠️ Intel Mac Support: Intel-based Mac builds are deprecated and provided without official support or active testing. We're keeping them available for now, but use them at your own risk. If you run into issues, feel free to open a ticket. While we can't guarantee a fix, we'd like to know if these builds are still being used!

---

## Logging

Off by default. When enabled, logs are written under `<userData>/logs/` — `main.log` from the host process and `core.log` from the vault worker. The worker's sink redacts known sensitive fields (passwords, keys, tokens, etc.) before writing to `core.log`. The host process logger does not redact, so treat anything passed to `logger.*` in `main.cjs` as on-disk-visible in `main.log`.

Three ways to enable:

- **In-app toggle** (Settings → Diagnostics → **Enable logs**). Persists across launches; toggling on/off resets existing log files for a clean session.
- **Launch flag:** pass `--enable-logging` at startup. Forces logging on regardless of the toggle.
- **Nightly builds** (`PearPass-nightly`): logging is on automatically and the in-app toggle is locked.

When logging is on, **Open logs folder** in the same screen reveals the directory.

---

## Testing

### Unit Testing

Run unit tests with Jest:

```bash
npm test
```


---

## Staging to Dev

Ensure the app runs correctly using `npm run dev`.

If successful, stage it, for example: `pear stage dev`.

Then run the app: `pear run pear://GENERATED_URL`.

Pear serves files from the `dist/` folder:

```html
<!-- index.html -->
<script type="module" src="./dist/app.js"></script>
```

The `src/` folder is for development and it's ignored in `package.json`:

```json
"ignore": [".github", "appling", ".git", ".gitignore", "packages", "src"]
```

---

## Workspace Dependencies

The following sibling modules must be present in the workspace (they are not declared as npm dependencies):

- [`@tetherto/tether-dev-docs`](../tether-dev-docs)
- [`@tetherto/pear-apps-lib-feedback`](../pear-apps-lib-feedback)
- [`@tetherto/pear-apps-lib-ui-react-hooks`](../pear-apps-lib-ui-react-hooks)
- [`@tetherto/pear-apps-utils-avatar-initials`](../pear-apps-utils-avatar-initials)
- [`@tetherto/pear-apps-utils-date`](../pear-apps-utils-date)
- [`@tetherto/pear-apps-utils-generate-unique-id`](../pear-apps-utils-generate-unique-id)
- [`@tetherto/pear-apps-utils-pattern-search`](../pear-apps-utils-pattern-search)
- [`@tetherto/pear-apps-utils-qr`](../pear-apps-utils-qr)
- [`@tetherto/pear-apps-utils-validator`](../pear-apps-utils-validator)
- [`@tetherto/pearpass-lib-constants`](../pearpass-lib-constants)
- [`@tetherto/pearpass-lib-data-export`](../pearpass-lib-data-export)
- [`@tetherto/pearpass-lib-data-import`](../pearpass-lib-data-import)
- [`@tetherto/pearpass-lib-ui-theme-provider`](../pearpass-lib-ui-theme-provider)
- [`@tetherto/pearpass-lib-vault`](../pearpass-lib-vault)
- [`@tetherto/pearpass-lib-vault-core`](../pearpass-lib-vault-core)
- [`@tetherto/pearpass-utils-password-check`](../pearpass-utils-password-check)
- [`@tetherto/pearpass-utils-password-generator`](../pearpass-utils-password-generator)

---

## Dependencies

- [Pear Runtime](https://pears.com/)
- [React](https://reactjs.org/)
- [Styled Components](https://styled-components.com/)
- [Lingui](https://lingui.dev/)
- [Redux](https://redux.js.org/)

---

## Related Projects

| Project                                                                                                          | Description                                |
| ---------------------------------------------------------------------------------------------------------------- | ------------------------------------------ |
| [`pearpass-app-mobile`](https://github.com/tetherto/pearpass-app-mobile)                                         | Mobile app for PearPass                    |
| [`pearpass-app-browser-extension`](https://github.com/tetherto/pearpass-app-browser-extension)                   | Browser extension for PearPass             |
| [`pearpass-lib-vault`](https://github.com/tetherto/pearpass-lib-vault)                                           | Vault management library                   |
| [`pearpass-lib-vault-core`](https://github.com/tetherto/pearpass-lib-vault-core)                                 | Bare worker and client for PearPass vaults |
| [`pearpass-lib-ui-react-components`](https://github.com/tetherto/pearpass-lib-ui-react-components)               | React UI component library                 |
| [`pearpass-lib-ui-react-native-components`](https://github.com/tetherto/pearpass-lib-ui-react-native-components) | React Native UI component library          |
| [`tether-dev-docs`](https://github.com/tetherto/tether-dev-docs)                                                 | Developer documentation and guides         |

---

## Contributing

We welcome contributions. See [`CONTRIBUTING.md`](./CONTRIBUTING.md) for the development workflow and coding conventions.

---

## License

This project is licensed under the Apache License, Version 2.0. See the [LICENSE](./LICENSE) file for details.


================================================
FILE: SECURITY.md
================================================
# Report a security issue

To report a security issue, please email [security-oss@tether.io](mailto:security-oss@tether.io).

We will respond within 5 working days of your report. Coordination and disclosure
will be handled here on GitHub (including using GitHub Security Advisory).

================================================
FILE: app.electron.tsx
================================================
/**
 * Electron-only entry for the renderer bundle (nodeIntegration: true).
 */
import { i18n } from '@lingui/core'
import { compileMessage } from '@lingui/message-utils/compileMessage'
import { I18nProvider } from '@lingui/react'
import { ThemeProvider } from '@tetherto/pearpass-lib-ui-theme-provider'
import {
  setPearpassVaultClient,
  VaultProvider
} from '@tetherto/pearpass-lib-vault'
import { createRoot } from 'react-dom/client'
import { ThemeProvider as UIKitProvider } from '@tetherto/pearpass-lib-ui-kit'

import './src/strict.css'
import { App } from './src/app/App'
import { LoadingProvider } from './src/context/LoadingContext'
import { ModalProvider } from './src/context/ModalContext'
import { RouterProvider } from './src/context/RouterContext'
import { AppHeaderContextProvider } from './src/context/AppHeaderContext'
import { ToastProvider } from './src/context/ToastContext'
import { messages } from './src/locales/en/messages.mjs'
import { getElectronConfig, getElectronVaultClient } from './src/electron'
import { createOrGetPearpassClient } from './src/services/createOrGetPearpassClient'
import { getNativeMessagingEnabled } from './src/services/nativeMessagingPreferences'
import { startNativeMessagingIPC } from './src/services/nativeMessagingIPCServer'
import { logger } from './src/utils/logger'
import { setFontsAndResetCSS } from './styles'
import { AutoLockProvider } from './src/hooks/useAutoLockPreferences'
import { DEBUG_MODE } from './src/constants/appConstants'

setFontsAndResetCSS()
i18n.setMessagesCompiler(compileMessage)
i18n.load('en', messages)
i18n.activate('en')

function renderApp() {
  const container = document.querySelector('#root')
  if (!container) throw new Error('Failed to find the root element')
  const root = createRoot(container)
  root.render(
    <UIKitProvider>
      <LoadingProvider>
        <ThemeProvider>
          <VaultProvider>
            <I18nProvider i18n={i18n}>
              <ToastProvider>
                <RouterProvider>
                  <AppHeaderContextProvider>
                    <AutoLockProvider>
                      <ModalProvider>
                        <App />
                      </ModalProvider>
                    </AutoLockProvider>
                  </AppHeaderContextProvider>
                </RouterProvider>
              </ToastProvider>
            </I18nProvider>
          </VaultProvider>
        </ThemeProvider>
      </LoadingProvider>
    </UIKitProvider>
  )
}

async function init() {
  const config = await getElectronConfig()
  const client = await getElectronVaultClient()
  if (!config || !client)
    throw new Error('Electron config or vault client missing')

  const api = window.electronAPI!
  ;(window as unknown as { Pear: object }).Pear = {
    config: {
      storage: config.storage,
      key: config.key,
      applink: config.applink || ''
    },
    updated: () => api.checkUpdated(),
    updates: (cb: (update?: unknown) => void) => {
      const unsub1 = api.onRuntimeUpdating(() => cb({}))
      const unsub2 = api.onRuntimeUpdated(() => cb({}))
      return () => {
        unsub1()
        unsub2()
      }
    },
    reload: () => window.location.reload(),
    restart: () => api.restart(),
    teardown: () => {}
  }

  // Seed shared PearPass client singleton so code that calls
  // createOrGetPearpassClient() without arguments (e.g. extension pairing)
  // can reuse this Electron vault client instance and storage path.
  createOrGetPearpassClient(client as any, config.storage, {
    debugMode: DEBUG_MODE
  })

  setPearpassVaultClient(client)
  if (getNativeMessagingEnabled()) {
    startNativeMessagingIPC(client as any).catch((err: unknown) => {
      logger.error('INDEX', 'Failed to start IPC server:', err)
    })
  }

  renderApp()
}

init()


================================================
FILE: appling/README.md
================================================
# PearPass Appling

Pear Appling installer for PearPass Desktop application.

## Overview

This is a self-contained installer (Appling) that bootstraps the Pear platform and installs PearPass Desktop. The installer:

1. Checks if the Pear platform is already installed
2. If not, downloads and installs the Pear platform via P2P bootstrap
3. Pre-loads the PearPass application
4. Launches PearPass once installation is complete

### Architecture

```
app.cjs                 # Entry point - calls install with app ID
lib/
├── install.cjs         # Main orchestration - UI and worker coordination
├── preflight.cjs       # Pre-installation checks and platform resolution
├── worker.cjs          # Background worker for heavy I/O operations
├── progress.cjs        # Multi-stage progress tracking
├── utils.cjs           # JSON encoding/decoding and formatting utilities
├── view.html.cjs       # HTML/CSS/JS for installer UI
└── icons/              # Platform-specific application icons
```

## Prerequisites

- Node.js 22.0.0 or later (required for `using` keyword support)
- `bare-build` (installed globally)

## Development Setup

```sh
# Install bare-build globally
npm install --global bare-build

# Install dependencies
npm install
```

## Building

### macOS (Apple Silicon)

```sh
bare-build --host=darwin-arm64 --icon lib/icons/darwin/icon.png app.cjs
```

### macOS (Intel)

```sh
bare-build --host=darwin-x64 --icon lib/icons/darwin/icon.png app.cjs
```

### Linux

```sh
bare-build --host=linux-x64 --icon lib/icons/linux/icon.png --package app.cjs
```

### Signed macOS Build

See the CI workflow (`.github/workflows/build-appling.yaml`) for full signing, notarization, and DMG creation instructions.

## Security Considerations

The macOS build requires specific entitlements (`entitlements.plist`) to function with Hardened Runtime. These entitlements are documented in the plist file itself. Key points:

- **Library validation is disabled** - Required for native Node.js addons
- **DYLD environment variables are allowed** - Required for Bare runtime

See `entitlements.plist` for detailed security documentation.

## License

Apache-2.0


================================================
FILE: appling/app.cjs
================================================
const { install } = require('./lib/install')

// When we have pear://pearpass, use this
// install("pearpass");

install('tywsat7gz8m65ejx4zjn3773pbdc4j8m66tukis8dgzekraymtzo')


================================================
FILE: appling/app.dev.cjs
================================================
const { install } = require('./lib/install')

// NOTE: Change the key when dev key changes

install('8ue4k8ooakpmwukzutkno5ca9wy3yono6mp4q69bph8tt11pfthy')


================================================
FILE: appling/app.staging.cjs
================================================
const { install } = require('./lib/install')

// NOTE: Change the key when staging key changes

install('8k5z91c8u7nycsjow5m9ppmw75jznm66oe13mwt94cbohuyksdeo')


================================================
FILE: appling/entitlements.plist
================================================
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<!--
  PearPass Appling - macOS Code Signing Entitlements

  SECURITY NOTICE:
  These entitlements are required for the Bare runtime and native Node.js addons to function
  correctly with macOS Hardened Runtime. They weaken the default security model and should
  be understood before deployment.

  Required Dependencies:
  - sodium-native: Cryptographic operations require loading native libraries
  - rocksdb-native: Database operations require loading native libraries
  - udx-native, simdle-native, quickbit-native: P2P networking components

  com.apple.security.cs.disable-library-validation:
    Allows the app to load dynamic libraries that are not signed by the same team ID.
    Required because native Node.js addons are compiled during npm install and are not
    code-signed with the application's signing identity.

  com.apple.security.cs.allow-dyld-environment-variables:
    Allows DYLD_* environment variables to affect the process.
    Required for the Bare runtime to properly load and resolve native modules.

  RISK ASSESSMENT:
  - These entitlements could allow library injection attacks if an attacker gains write
    access to the application bundle or user's library paths.
  - Mitigated by: macOS notarization, Gatekeeper, and the app's limited file system access.
  - The Pear platform's P2P code is loaded through its own verified channels, not through
    these library loading mechanisms.

  ALTERNATIVES CONSIDERED:
  - Bundling pre-signed native modules: Would break cross-platform build consistency
  - Removing native dependencies: Would remove core functionality (encryption, database, P2P)
-->
<plist version="1.0">
<dict>
    <!-- Required for native Node.js addons (sodium-native, rocksdb-native, etc.) -->
    <key>com.apple.security.cs.disable-library-validation</key>
    <true/>
    <!-- Required for Bare runtime module resolution -->
    <key>com.apple.security.cs.allow-dyld-environment-variables</key>
    <true/>
    <key>com.apple.security.cs.allow-jit</key>
    <true/>
    <key>com.apple.security.cs.allow-unsigned-executable-memory</key>
    <true/>
</dict>
</plist>


================================================
FILE: appling/lib/install.cjs
================================================
const Thread = require("bare-thread");
const { App, Screen, Window, WebView } = require("fx-native");
const appling = require("appling-native");
const { encode, decode } = require("./utils");
const { preflight } = require("./preflight");
const html = require("./view.html");

const WINDOW_HEIGHT = 548;
const WINDOW_WIDTH = 500;

async function install(id, opts = {}) {
  const { platform = "pzcjqmpoo6szkoc4bpkw65ib9ctnrq7b6mneeinbhbheihaq6p6o" } =
    opts;

  // Preflight check - determines if installation is needed
  const preflightResult = await preflight(id);

  // If app was already installed and launched, exit immediately
  if (preflightResult.launched) {
    Bare.exit();
    return;
  }

  // Use the lock with explicit resource management
  using lock = preflightResult.lock;

  const config = {
    dir: lock.dir,
    platform,
    link: `pear://${id}`,
  };

  const app = App.shared();

  let window;
  let view;

  function onViewMessage(message) {
    const msg = message.toString();
    switch (msg) {
      case "quit":
        window.close();
        break;
      case "install":
        app.broadcast(encode({ type: "install" }));
        break;
      case "launch": {
        lock.unlock();
        const appInstance = new appling.App(id);
        appInstance.open();
        window.close();
        Bare.exit();
        break;
      }
    }
  }

  function onWorkerMessage(message) {
    const msg = decode(message);
    if (!msg) return;

    switch (msg.type) {
      case "ready":
        app.broadcast(encode({ type: "config", data: config }));
        break;
      case "download":
        view.postMessage({ type: "progress", data: msg.data });
        break;
      case "complete":
        view.postMessage({ type: "state", state: "complete" });
        break;
      case "error":
        console.error("[install] Worker error:", msg.error);
        view.postMessage({ type: "state", state: "error" });
        break;
    }
  }

  // Track worker thread for cleanup
  let workerThread;

  app
    .on("launch", () => {
      workerThread = new Thread(require.resolve("./worker"));

      const { width, height } = Screen.main().getBounds();

      window = new Window(
        (width - WINDOW_WIDTH) / 2,
        (height - WINDOW_HEIGHT) / 2,
        WINDOW_WIDTH,
        WINDOW_HEIGHT,
        { frame: false },
      );

      view = new WebView(0, 0, WINDOW_WIDTH, WINDOW_HEIGHT);
      view.on("message", onViewMessage).loadHTML(html);

      window.appendChild(view);
      window.show();
    })
    .on("terminate", () => {
      // Terminate worker thread if it exists
      if (workerThread) {
        try {
          workerThread.terminate();
        } catch (err) {
          // Ignore termination errors
        }
      }
      if (window) {
        window.destroy();
      }
    })
    .on("message", onWorkerMessage)
    .run();
}

module.exports = { install };


================================================
FILE: appling/lib/preflight.cjs
================================================
const appling = require('appling-native')

/**
 * Performs pre-installation checks to determine if the app is already installed.
 *
 * @param {string} id - The Pear application ID (z32 encoded public key)
 * @returns {Promise<{lock: object, needsInstall: boolean, launched: boolean}>}
 *   - lock: The installation lock (null if app was launched)
 *   - needsInstall: true if installation is required
 *   - launched: true if the app was already installed and has been launched
 */
async function preflight(id) {
  // Acquire lock without specifying dir - appling-native uses default location
  const lock = await appling.lock()

  let platform
  try {
    platform = await appling.resolve() //lock.dir if infinite loop
  } catch {
    // Platform not found - installation required
    return { lock, needsInstall: true, launched: false }
  }

  const ready = platform.ready(`pear://${id}`)

  if (ready === false) {
    return { lock, needsInstall: true, launched: false }
  }

  // App is already installed and ready - launch it directly
  await lock.unlock()
  platform.launch(id)

  // Return sentinel value indicating app was launched
  // Caller should exit the process after receiving this
  return { lock: null, needsInstall: false, launched: true }
}

module.exports = { preflight }


================================================
FILE: appling/lib/progress.cjs
================================================
const prettyBytes = require('prettier-bytes')

const { encode } = require('./utils')

/**
 * Multi-stage progress tracker with weighted percentages.
 * Broadcasts progress updates via the app's IPC mechanism.
 */
// NOTE: Assumes updates come from a trusted worker.
// Do not expose Progress.update() to untrusted input.
class Progress {
  /**
   * Creates a new Progress tracker.
   * @param {object} app - The fx-native App instance for broadcasting
   * @param {number[]} stages - Array of stage weights (should sum to 1.0)
   * @example new Progress(app, [0.3, 0.7]) // 30% for stage 0, 70% for stage 1
   */
  constructor(app, stages = [1]) {
    this.app = app
    this.stages = stages
    this.values = Array(stages.length).fill(0)
    this.speed = ''
    this.peers = 0
    this.total = 0
    this.currentStage = 0
    this.stageBytes = Array(stages.length).fill(0)

    // Validate that stage weights sum to approximately 1.0
    const sum = stages.reduce((a, b) => a + b, 0)
    if (Math.abs(sum - 1) > 0.001) {
      console.warn(
        `[Progress] Stage weights sum to ${sum.toFixed(3)}, expected 1.0. ` +
          `Progress percentage may not reach 100%.`
      )
    }
  }

  _broadcast() {
    const bytes = this.stageBytes.reduce((sum, b) => sum + b, 0)
    this.app.broadcast(
      encode({
        type: 'download',
        data: {
          speed: this.speed,
          peers: this.peers,
          progress: this.total,
          stage: this.currentStage,
          bytes: prettyBytes(bytes)
        }
      })
    )
  }

  _compute() {
    const v = this.stages.reduce((sum, per, i) => sum + per * this.values[i], 0)
    this.total = Math.round(v * 100)
  }

  update(u, stage = 0) {
    if (u.speed !== undefined) this.speed = u.speed
    if (u.peers !== undefined) this.peers = u.peers
    if (u.bytes !== undefined) this.stageBytes[stage] = u.bytes
    if (u.progress !== undefined) this.stage(stage, u.progress)
  }

  stage(stage, value) {
    if (stage < 0 || stage >= this.values.length) return
    this.currentStage = stage
    this.values[stage] = Math.min(1, Math.max(0, value))
    this._compute()
    this._broadcast()
  }

  complete() {
    this.values = this.values.map(() => 1)
    this._compute()
    this._broadcast()
  }
}

module.exports = { Progress }


================================================
FILE: appling/lib/utils.cjs
================================================
const prettyBytes = require('prettier-bytes')

/**
 * Encodes an object to JSON string for IPC messaging.
 * @param {object} msg - The message object to encode
 * @returns {string} JSON string representation
 */
function encode(msg) {
  return JSON.stringify(msg)
}

/**
 * Decodes a JSON string from IPC messaging.
 * @param {string|Buffer} msg - The message to decode
 * @returns {object|null} Parsed object, or null if parsing fails
 */
function decode(msg) {
  try {
    return JSON.parse(msg.toString())
  } catch (err) {
    console.error('[decode] Failed to parse JSON:', err.message)
    return null
  }
}

/**
 * Formats download progress data for display.
 * Handles two different progress object formats from the Pear ecosystem.
 * @param {object} u - Progress update object
 * @returns {object} Formatted progress data with speed, progress, peers, bytes
 */
function format(u) {
  // Guard against null/undefined input
  if (!u) {
    return {
      speed: undefined,
      progress: undefined,
      peers: undefined,
      bytes: undefined
    }
  }

  // Format for Hyperswarm updater (has drive.core property)
  if (u.drive?.core) {
    return {
      speed: prettyBytes(u.downloadSpeed()) + '/s',
      progress: u.downloadProgress,
      peers: u.drive.core.peers.length,
      bytes: u.downloadedBytes
    }
  }

  // Format for Bootstrap updater (has direct properties)
  return {
    speed:
      u.downloadSpeed === 0 ? undefined : prettyBytes(u.downloadSpeed) + '/s',
    progress: u.downloadProgress === 0 ? undefined : u.downloadProgress,
    peers: u.peers === 0 ? undefined : u.peers,
    bytes: u.downloadedBytes
  }
}

module.exports = { encode, decode, format }


================================================
FILE: appling/lib/view.html.cjs
================================================
const AUTO_LAUNCH = true
const SLOW_TIMEOUT = 180000 // 3 minutes

// Simple inline SVG for splash - can be replaced with actual PearPass logo
const splashSvg = require('./images/logo.svg', { with: { type: 'text' } })

// Pear logo SVG
const pearSvg = require('./images/pear.svg', { with: { type: 'text' } })

const html = String.raw

module.exports = html`
  <style>
    :root {
      --color-white: #ffffff;
      --color-green-400: #33f59a;
      --color-grey-100: #ced3dc;
      --color-grey-500: #30333f;
      --color-grey-800: #151823;
      --color-grey-950: #00020a;
      --color-blue-400: #26d2e8;
      --color-blue-500: #0cb6ce;
      --color-blue-600: #0d91ad;
      --color-blue-950: #0a3342;
      --color-red-300: #ff6c72;
      --color-red-400: #ff1831;
      --color-gradient-background: linear-gradient(
        180deg,
        #687c26 0%,
        #1e2211 100%
      );
    }

    html {
      font-size: 16px;
      overflow: hidden;
      -webkit-font-smoothing: antialiased;
      -moz-osx-font-smoothing: grayscale;
      background-color: #121212;
    }

    body {
      margin: 0;
      background: #121212;
      color: var(--color-white);
      font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto,
        Oxygen, Ubuntu, Cantarell, sans-serif;
      position: relative;
      overflow: hidden;
    }

    /* Blurred ellipse background effect */
    body::before {
      content: '';
      position: absolute;
      top: -34vh;
      left: 3.2%;
      width: 93.6vw;
      height: 52.6vh;
      background: #b0d944;
      border-radius: 50%;
      filter: blur(150px);
      opacity: 0.3;
      z-index: 0;
      pointer-events: none;
    }

    img {
      flex: 1;
      padding: 0 100px;
    }

    h1 {
      font-weight: 600;
      font-size: 1.25rem;
      margin: 0;
    }

    p {
      margin: 0;
      font-weight: 400;
      font-size: 0.875rem;
    }

    span {
      font-weight: 400;
      font-size: 0.75rem;
      color: var(--color-grey-100);
    }

    main {
      display: flex;
      gap: 0.5rem;
      align-items: center;
      flex-direction: column;
      justify-content: center;
      padding: 0 3.8rem;
      height: 100vh;
      position: relative;
      z-index: 1;
    }

    header {
      display: flex;
      align-items: center;
      flex-direction: column;
      text-align: center;
      margin-bottom: 5.377rem;
    }

    header svg {
      width: 215px;
      height: 45.2px;
    }

    article {
      display: flex;
      flex-direction: column;
      align-items: center;
      text-align: center;
      gap: 0.5rem;
      width: 100%;
    }

    footer {
      gap: 0.5rem;
      text-align: center;
      display: flex;
      align-items: center;
      flex-direction: column;
    }

    footer svg {
      width: 35px;
      height: 47.175px;
      aspect-ratio: 35 / 47.17;
    }

    button {
      background-color: #9fc131;
      color: var(--color-grey-950);
      font-size: 0.938rem;
      font-weight: 600;
      height: 2.75rem;
      padding: 0 2.5rem;
      border-radius: 3.125rem;
      border: none;
      letter-spacing: -0.02em;
      margin-top: 1.125rem;
      margin-bottom: 3.125rem;
      transition: background-color 0.2s ease;
      white-space: nowrap;
      cursor: pointer;
    }

    button:hover {
      background-color: #b1cb58;
    }

    button:active {
      background-color: #b1cb58;
    }

    /* Accessibility: provide visible focus indicator for keyboard navigation */
    button:focus {
      outline: 2px solid var(--color-blue-400);
      outline-offset: 2px;
    }

    button:focus:not(:focus-visible) {
      outline: none;
    }

    button:focus-visible {
      outline: 2px solid var(--color-blue-400);
      outline-offset: 2px;
    }

    button.secondary {
      color: var(--color-blue-400);
      border: 1px solid var(--color-blue-400);
      background-color: transparent;
    }

    button.secondary:hover {
      color: var(--color-blue-400);
      border-color: var(--color-blue-500);
      background-color: transparent;
    }

    .hidden {
      display: none !important;
    }

    .button-group {
      display: flex;
      gap: 1rem;
      margin-top: 1.125rem;
      margin-bottom: 0;
      width: 100%;
    }

    .button-group button {
      flex: 1;
      min-width: 0;
      margin: 0;
      padding: 0 1rem;
    }

    .message {
      font-size: 0.8rem;
      width: 100%;
      text-align: left;
    }

    .message.error {
      color: var(--color-red-400);
    }

    .status-line {
      display: flex;
      justify-content: space-between;
      align-items: start;
      width: 100%;
      gap: 0.3rem;
      min-height: 1.5rem;
    }

    .status-line .message {
      margin: 0;
      flex: 1;
      text-align: left;
    }

    .stats {
      font-variant-numeric: tabular-nums;
      font-size: 0.8rem;
      display: flex;
      justify-content: flex-end;
      gap: 0.3rem;
      white-space: nowrap;
      margin: 0;
      margin-left: auto;
    }

    .status {
      width: 100%;
      height: 130px;
      display: flex;
      flex-direction: column;
      justify-content: center;
      align-items: center;
      gap: 0.5rem;
    }

    @keyframes indeterminate {
      0% {
        transform: translateX(-100%);
      }
      100% {
        transform: translateX(400%);
      }
    }

    .progress {
      width: 100%;
      height: 6px;
      background-color: var(--color-grey-500);
      border-radius: 4px;
      overflow: hidden;
    }

    .progress > div {
      height: 100%;
      width: 0%;
      border-radius: 4px;
      background-color: var(--color-blue-400);
      animation: none;
    }

    .progress.indeterminate > div {
      width: 25%;
      animation: indeterminate 1.5s ease-in-out infinite;
    }

    .progress.red > div {
      background-color: var(--color-red-400);
    }

    .progress.green > div {
      background-color: var(--color-green-400);
    }

    .progress.complete > div {
      width: 100%;
      animation: none;
      background-color: var(--color-green-400);
    }

    .progress.determinate > div {
      animation: none;
      width: 0%;
    }

    .progress.determinate.transitioning > div {
      transition: width 0.3s ease;
    }
  </style>

  <main>
    <header>${splashSvg}</header>
    <article>
      <h1 id="title">Welcome to PearPass</h1>
      <p id="message">
        Ready to start the installation. This will only take a moment.
      </p>

      <button id="installBtn">Install PearPass</button>

      <div id="status" class="status hidden">
        <div
          id="progress"
          class="progress"
          role="progressbar"
          aria-valuenow="0"
          aria-valuemin="0"
          aria-valuemax="100"
          aria-label="Installation progress"
        >
          <div></div>
        </div>
        <div id="statusLine" class="status-line">
          <p id="warning" class="message" role="status" aria-live="polite"></p>
          <p id="stats" class="stats" aria-label="Download statistics"></p>
        </div>
        <div id="buttonGroup" class="button-group hidden">
          <button id="quitBtn" class="secondary" aria-label="Quit installation">
            Quit
          </button>
          <button id="retryBtn" aria-label="Retry installation">
            Retry installation
          </button>
        </div>
        <button
          id="launchBtn"
          class="hidden"
          style="margin-top: 1.125rem; margin-bottom: 0;"
        >
          Launch PearPass
        </button>
      </div>
    </article>
    <footer>
      ${pearSvg}
      <span>Powered by Pear</span>
    </footer>
  </main>
  <script>
    const SLOW_TIMEOUT = ${SLOW_TIMEOUT}
    const AUTO_LAUNCH = ${AUTO_LAUNCH}

    const elements = {
      title: document.getElementById('title'),
      message: document.getElementById('message'),
      status: document.getElementById('status'),
      progress: document.getElementById('progress'),
      stats: document.getElementById('stats'),
      warning: document.getElementById('warning'),
      buttonGroup: document.getElementById('buttonGroup'),
      installBtn: document.getElementById('installBtn'),
      quitBtn: document.getElementById('quitBtn'),
      retryBtn: document.getElementById('retryBtn'),
      launchBtn: document.getElementById('launchBtn')
    }

    let timer = null

    function startTimer() {
      timer = setTimeout(() => setState('slow'), SLOW_TIMEOUT)
    }

    function onMessage(element, fn) {
      element.addEventListener('message', fn)
    }

    function onClick(element, fn) {
      element.addEventListener('click', fn)
    }

    function resetElements(e) {
      e.installBtn.classList.add('hidden')
      e.status.classList.remove('hidden')
      e.progress.classList.remove('red', 'green', 'complete')
      e.warning.textContent = ''
      e.warning.classList.add('hidden')
      e.warning.classList.remove('error')
      e.buttonGroup.classList.add('hidden')
      e.launchBtn.classList.add('hidden')
    }

    function setProgress({ speed, peers, progress, stage, bytes }) {
      // Update stats
      if (bytes || speed || peers !== undefined) {
        const parts = [
          bytes && '<span class="bytes">' + bytes + '</span>',
          speed && '<span class="speed">' + speed + '</span>',
          peers !== undefined &&
            '<span class="peers">' +
              peers +
              ' ' +
              (peers === 1 ? 'peer' : 'peers') +
              '</span>'
        ].filter(Boolean)
        elements.stats.innerHTML = parts.join('<span>•</span>')
      }

      // Update progress
      if (progress !== undefined) {
        elements.progress.classList.remove('indeterminate')
        elements.progress.classList.add('determinate')
        const progressBar = elements.progress.querySelector('div')

        // Update ARIA attributes for accessibility
        elements.progress.setAttribute('aria-valuenow', Math.round(progress))

        // Enable transitions after first frame
        if (progress > 0) {
          elements.progress.classList.add('transitioning')
        }

        if (progress === 0) {
          elements.warning.textContent = 'Starting platform installation'
          elements.warning.classList.remove('hidden', 'error')
        }

        progressBar.style.width = progress + '%'
      }

      if (progress !== 0) {
        if (stage === 0) {
          elements.warning.textContent = 'Installing platform...'
          elements.warning.classList.remove('hidden', 'error')
        } else if (stage === 1) {
          elements.warning.textContent = 'Platform ready, installing app...'
          elements.warning.classList.remove('hidden', 'error')
        }
      }
    }

    function setState(state) {
      const {
        title,
        message,
        installBtn,
        progress,
        warning,
        buttonGroup,
        launchBtn
      } = elements

      resetElements(elements)
      clearTimeout(timer)
      timer = null

      switch (state) {
        case 'installing':
          title.textContent = 'Welcome to PearPass'
          message.textContent = 'It will launch once it is done.'
          break
        case 'slow':
          title.textContent = 'Welcome to PearPass'
          message.textContent = 'It will launch once it is done.'
          warning.textContent =
            "It's taking a bit of time, please check your connection."
          warning.classList.remove('hidden')
          break
        case 'error':
          title.textContent = 'Welcome to PearPass'
          message.textContent = 'It will launch once it is done.'
          progress.classList.add('red')
          warning.textContent = "Installation didn't complete."
          warning.classList.remove('hidden')
          warning.classList.add('error')
          buttonGroup.classList.remove('hidden')
          break
        case 'complete':
          title.textContent = 'Installation complete!'
          progress.classList.add('complete')
          if (AUTO_LAUNCH) {
            message.textContent = 'PearPass will launch shortly'
            setTimeout(() => bridge.postMessage('launch'), 500)
          } else {
            message.textContent = 'PearPass is ready to launch.'
            launchBtn.classList.remove('hidden')
          }
          break
      }
    }

    onMessage(bridge, (event) => {
      const { type, state, data } = event.data || {}
      switch (type) {
        case 'state':
          setState(state)
          break
        case 'progress':
          setProgress(data)
          break
      }
    })

    onClick(elements.installBtn, () => {
      setState('installing')
      bridge.postMessage('install')
      startTimer()
    })

    onClick(elements.retryBtn, () => {
      setState('installing')
      bridge.postMessage('install')
      startTimer()
    })

    onClick(elements.quitBtn, () => {
      bridge.postMessage('quit')
    })

    onClick(elements.launchBtn, () => {
      bridge.postMessage('launch')
    })
  </script>
`


================================================
FILE: appling/lib/worker.cjs
================================================
const appling = require('appling-native')
const { App } = require('fx-native')
const bootstrap = require('pear-updater-bootstrap')

const { Progress } = require('./progress')
const { encode, decode, format } = require('./utils')

const app = App.shared()

let config
let platform
let installing = false // Guard against concurrent installation

function setup(data) {
  config = data
}

/**
 * Validates that config has all required fields.
 * @returns {string|null} Error message if invalid, null if valid
 */
function validateConfig() {
  if (!config) {
    return 'Configuration not received - setup() was not called'
  }
  if (!config.dir) {
    return "Configuration missing required 'dir' field"
  }
  if (!config.platform) {
    return "Configuration missing required 'platform' field"
  }
  if (!config.link) {
    return "Configuration missing required 'link' field"
  }
  return null
}

async function install() {
  // Prevent concurrent installation attempts
  if (installing) {
    return
  }

  // Validate configuration before proceeding
  const configError = validateConfig()
  if (configError) {
    console.error('[worker] Configuration error:', configError)
    app.broadcast(encode({ type: 'error', error: configError }))
    return
  }

  installing = true
  const progress = new Progress(app, [0.3, 0.7])
  let platformFound = false
  let bootstrapInterval = null

  try {
    try {
      platform = await appling.resolve(config.dir)
      platformFound = true
    } catch {
      // Platform not found - bootstrap it
      await bootstrap(config.platform, config.dir, {
        lock: false,
        onupdater: (u) => {
          bootstrapInterval = setInterval(() => {
            progress.update(format(u))
            if (u.downloadProgress === 1) {
              clearInterval(bootstrapInterval)
            }
          }, 250)
        }
      })
      platform = await appling.resolve(config.dir)
    }

    if (platformFound) {
      progress.stage(0, 1)
    }

    progress.update({ progress: 0, speed: '', peers: 0, bytes: 0 }, 1)

    await platform.preflight(config.link, (u) => {
      progress.update(format(u), 1)
    })

    progress.complete()
    app.broadcast(encode({ type: 'complete' }))
  } catch (e) {
    console.error('[worker] Installation error:', e.message)
    app.broadcast(encode({ type: 'error', error: e.message }))
  } finally {
    // Always reset installing flag to allow retry
    installing = false
    if (bootstrapInterval) {
      clearInterval(bootstrapInterval)
    }
  }
}

app.on('message', async (message) => {
  const msg = decode(message)

  // Handle decode failure (malformed JSON)
  if (!msg) {
    return
  }

  switch (msg.type) {
    case 'config':
      setup(msg.data)
      break
    case 'install':
      await install()
      break
  }
})

app.broadcast(encode({ type: 'ready' }))


================================================
FILE: appling/package.json
================================================
{
  "private": true,
  "name": "PearPass",
  "version": "1.0.0",
  "description": "Pear Appling for PearPass",
  "engines": {
    "node": ">=22.0.0"
  },
  "exports": {
    "./package": "./package.json"
  },
  "files": [
    "app.cjs",
    "lib"
  ],
  "scripts": {
    "test": "npm run lint",
    "lint": "prettier . --check"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/tetherto/pearpass-app-desktop.git"
  },
  "author": "Tether Data S.A. de C.V.",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/tetherto/pearpass-app-desktop/issues"
  },
  "homepage": "https://github.com/tetherto/pearpass-app-desktop#readme",
  "dependencies": {
    "appling-native": "^1.4.0",
    "bare-thread": "^1.1.3",
    "fx-native": "^1.1.3",
    "pear-updater-bootstrap": "^2.3.0",
    "prettier-bytes": "^1.0.4"
  },
  "devDependencies": {
    "prettier": "^3.6.2",
    "prettier-config-holepunch": "^2.0.0"
  }
}


================================================
FILE: babel.config.cjs
================================================
module.exports = {
  presets: [
    [
      '@babel/preset-env',
      {
        targets: { node: 'current' },
        modules: 'commonjs'
      }
    ],
    '@babel/preset-react',
    '@babel/preset-typescript'
  ],
  env: {
    test: {
      // Compile `css.create(...)` / `html.*` calls from react-strict-dom and
      // @tetherto/pearpass-lib-ui-kit the same way the production bundler does
      // (see `scripts/bundle-renderer.mjs`). Without this, evaluating those
      // modules in Jest throws "Styles must be compiled by '@stylexjs/babel-plugin'".
      presets: [
        [
          'react-strict-dom/babel-preset',
          {
            debug: false,
            dev: false,
            rootDir: process.cwd(),
            platform: 'web'
          }
        ]
      ]
    }
  }
}


================================================
FILE: babel.strict-dom.cjs
================================================
const dev = process.env.NODE_ENV !== 'production'

module.exports = {
  babelrc: false,
  configFile: false,
  parserOpts: {
    plugins: ['typescript', 'jsx']
  },
  presets: [
    [
      'react-strict-dom/babel-preset',
      {
        debug: dev,
        dev,
        rootDir: process.cwd(),
        platform: 'web'
      }
    ]
  ]
}


================================================
FILE: build-assets/win/AppxManifest.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<Package
  xmlns="http://schemas.microsoft.com/appx/manifest/foundation/windows10"
  xmlns:uap="http://schemas.microsoft.com/appx/manifest/uap/windows10"
  xmlns:rescap="http://schemas.microsoft.com/appx/manifest/foundation/windows10/restrictedcapabilities"
  xmlns:uap3="http://schemas.microsoft.com/appx/manifest/uap/windows10/3"
  xmlns:desktop="http://schemas.microsoft.com/appx/manifest/desktop/windows10"
  xmlns:desktop6="http://schemas.microsoft.com/appx/manifest/desktop/windows10/6"
  IgnorableNamespaces="rescap desktop6"
>
  <Identity 
  Name="PearPass" 
  Version="1.6.0.0" 
  Publisher="CN=&quot;Tether Operations, SA de CV&quot;, O=&quot;Tether Operations, SA de CV&quot;, L=San Salvador, C=SV, SERIALNUMBER=2025120324, OID.2.5.4.15=Private Organization, OID.1.3.6.1.4.1.311.60.2.1.3=SV" 
  ProcessorArchitecture="x64" 
  />
  <Properties>
    <DisplayName>PearPass</DisplayName>
    <PublisherDisplayName>PearPass</PublisherDisplayName>
    <Description>PearPass password manager</Description>
    <!-- Icon lives under the MSIX assets/ folder -->
    <Logo>assets\PearPass.png</Logo>
    <desktop6:FileSystemWriteVirtualization>disabled</desktop6:FileSystemWriteVirtualization>
    <desktop6:RegistryWriteVirtualization>disabled</desktop6:RegistryWriteVirtualization>
  </Properties>
  <Resources>
    <Resource Language="en-US" />
  </Resources>
  <Dependencies>
    <TargetDeviceFamily Name="Windows.Desktop" MinVersion="10.0.19045.0" MaxVersionTested="10.0.19045.0" />
  </Dependencies>
  <Capabilities>
    <rescap:Capability Name="runFullTrust" />
    <!-- Required when disabling virtualization flags on desktop -->
    <rescap:Capability Name="unvirtualizedResources" />
  </Capabilities>
  <Applications>
    <Application Id="App" Executable="app\PearPass.exe" EntryPoint="Windows.FullTrustApplication">
      <uap:VisualElements
        DisplayName="PearPass"
        Description="PearPass password manager"
        Square150x150Logo="assets\PearPass.png"
        Square44x44Logo="assets\PearPass.png"
        BackgroundColor="transparent" />
      <Extensions>
        <uap3:Extension Category="windows.appExecutionAlias" Executable="app\PearPass.exe" EntryPoint="Windows.FullTrustApplication">
          <uap3:AppExecutionAlias>
            <desktop:ExecutionAlias Alias="PearPass.exe" />
          </uap3:AppExecutionAlias>
        </uap3:Extension>
      </Extensions>
    </Application>
  </Applications>
</Package>



================================================
FILE: docs/Electron-Packaging-And-Runtime.md
================================================
# Electron packaging and runtime

This document describes how the PearPass desktop app is built, packaged, and how the main process, worklet (vault), and renderer communicate.

---

## 1. Architecture overview

```
┌─────────────────────────────────────────────────────────────────┐
│  Renderer (React)                                               │
│  - Uses window.electronAPI (from preload) for vault & runtime   │
└────────────────────────────┬────────────────────────────────────┘
                             │ IPC (vault:invoke, runtime:*, get-app-path)
                             ▼
┌─────────────────────────────────────────────────────────────────┐
│  Main process (electron/main.cjs)                               │
│  - Creates window, preload, BrowserWindow                       │
│  - Starts worklet via pear-runtime or bare-sidecar              │
│  - Registers IPC handlers; forwards vault calls to vaultClient  │
└────────────────────────────┬────────────────────────────────────┘
                             │ stdio / IPC pipe
                             ▼
┌─────────────────────────────────────────────────────────────────┐
│  Worklet (Bare sidecar)                                         │
│  - Runs vault logic (pearpass-lib-vault-core worklet)           │
│  - Dev: app.js (ESM). Packaged: app.cjs (CJS bundle)            │
└─────────────────────────────────────────────────────────────────┘
```

- **Renderer** never talks to the worklet directly. It calls `window.electronAPI.vaultInvoke(method, args)` (and runtime helpers). The **main process** receives those and forwards them to **PearpassVaultClient**, which speaks to the worklet over a pipe.
- The **worklet** runs in a separate process (Bare runtime). It uses `bare-*` modules and native addons; the main process only spawns it and connects IPC.

---

## 2. Main process (electron/main.cjs)

- **Entry:** `main` in package.json points to `electron/main.cjs`.
- **On ready:** Sets log path, registers IPC, then starts the runtime via `startRuntime()` and finally creates the main window.
- **Runtime start:**
  - If `runtime-config.cjs.upgrade` is set: uses **pear-runtime** (P2P OTA), configures storage based on `pear-runtime-legacy-storage`, and calls `PearRuntime.run(workletPath)` to launch the vault worklet as a sidecar.
  - If no upgrade link is set: uses **bare-sidecar** only (`startWorkletOnly()`), spawns the worklet with `new Sidecar(workletPath)` and runs without P2P updates.
- **Storage layout:** Tries to reuse existing Pear platform storage via `pear-runtime-legacy-storage`. If none is found, it falls back to `app.getPath('userData')/app-storage/by-dkey/<upgrade-key>`.
- **Flatpak compatibility:** `electron/flatpak-paths.cjs` wraps both `app.getPath('userData')` and any legacy Pear storage path with `getSandboxSafePath()`. Inside Flatpak, host-mapped XDG paths under `~/.var/app/...` are remapped into `~/.config/...` compatibility paths so the vault worklet accepts them.
- **Packaged app:** With `asar: false` all code and `node_modules` live under `Contents/Resources/app/` on macOS, so the worklet and renderer resolve modules from the real filesystem (no `app.asar` indirection).
- **IPC:** Handles `get-app-path`, `runtime:getConfig`, `runtime:applyUpdate`, `runtime:restart`, `runtime:checkUpdated`, and `vault:invoke`. Vault methods are forwarded to `vaultClient`; Buffers are serialized as `{ __base64 }`. It also listens for `pearRuntime.updater` events and forwards `runtime:updating` / `runtime:updated` to the renderer to drive the OTA UI.

---

## 3. Worklet: dev vs packaged

The vault worklet lives in `@tetherto/pearpass-lib-vault-core` (Git dependency) under `src/worklet/`. It is loaded in two different ways so it works in both dev and packaged app.

### 3.1 Dev

- **Path:** `getWorkletPath()` returns  
  `app.getAppPath()/node_modules/@tetherto/pearpass-lib-vault-core/src/worklet/app.js`.
- **Format:** ESM (`app.js`). The Bare loader in dev can run ESM and resolve Node built-ins to its own shims.
- **No bundle:** Dependencies are required from the real `node_modules` tree.

### 3.2 Packaged

- **Path:** `getWorkletPath()` returns  
  `process.resourcesPath/app/node_modules/@tetherto/pearpass-lib-vault-core/src/worklet/app.cjs`.
- **Format:** CommonJS bundle (`app.cjs`). The Bare runtime used in the packaged app loads the entry as CJS; giving it ESM `app.js` would throw “Cannot use import statement outside a module”.
- **Bundle:** Produced by `scripts/build.worklet.mjs` (see below). Only the worklet **source** (relative imports) is bundled; all `node_modules` are external so Bare resolves them at runtime and native addons work.

---

## 4. Worklet build (scripts/build.worklet.mjs)

- **Runs as part of `npm run build`** (before `tsc` and the renderer bundle).
- **Input:** `node_modules/@tetherto/pearpass-lib-vault-core/src/worklet/app.js` (ESM).
- **Output:** `node_modules/@tetherto/pearpass-lib-vault-core/src/worklet/app.cjs` (single CJS file).
- **Behaviour (current esbuild config):**
  - `entryPoints`: the ESM worklet entry; `bundle: true`, `platform: 'node'`, `format: 'cjs'`, `target: 'node18'`.
  - **Externalize Node built-ins and native-heavy modules:** `fs`, `path`, `os`, `net`, `crypto`, `child_process`, `fs/promises`, `require-addon`, `fs-native-extensions`, `sodium-native` are marked as `external` so they resolve at runtime from `node_modules`.
- **Result:** A CJS bundle that contains only the worklet code; at runtime Bare loads its dependencies from `node_modules` in the packaged app.

---

## 5. Packaging (no asar; mac = electron-builder, win = electron-forge)

- **asar:** Disabled (`"asar": false` in `build`). All app code and `node_modules` are real files on disk (no `app.asar`), so the worklet and renderer always resolve modules from the filesystem.
- **Why no asar:** Electron patches the Node `fs` module so any access to `*.asar` is routed through its ASAR reader. During OTA on macOS, `pear-runtime-updater` mirrors a partially written `app.asar` into the `next` directory; Electron’s patched `fs` then tries to treat that in‑progress file as a valid ASAR and throws `Error: Invalid package ...app.asar`. Turning asar off avoids this class of error and lets the updater see only plain files.

### 5.1 macOS (electron-builder)

- **Tooling:** `electron-builder@23.6.0`.
- **Build commands:** `npm run dist:mac` (local) and `npm run dist:mac:ci` (CI).
- **Pipeline:**
  - `npm run build` → worklet bundle + `tsc` + renderer bundle (`dist/renderer.bundle.js`).
  - `npx electron-builder --mac` → `dist/mac-arm64/PearPass.app` + DMG.
  - CI uses `scripts/notarize.cjs` as an `afterSign` hook (`@electron/notarize` + `notarytool`) to sign and notarize the app.
  - After that, `PearPass.app` is copied into `out/darwin-arm64/` and `pear:build:darwin` produces the Pear drive layout (`by-arch/darwin-arm64/app/PearPass.app/...`).

### 5.2 Windows (electron-forge, MSIX)

- **Tooling:** **Electron Forge** for Windows packaging (electron-builder does not support MSIX).
- **Build:** Forge produces an MSIX package for the Windows desktop app; CI then stages that MSIX into the Pear drive for the `win32-x64` arch so `PearRuntime` on Windows can install it via `MSIXManager`.
- **Pear layout:** The staged drive contains `by-arch/win32-x64/app/<name>.msix`, where `<name>` matches the `name` passed to `PearRuntime` in `electron/main.cjs`.

---

## 6. Preload (electron/preload.cjs)

- **Attached to the renderer** via `webPreferences.preload` (with `nodeIntegration: true`, `contextIsolation: false`).
- **Responsibilities:**
  1. **App path for fs-native-extensions:** Sends `get-app-path` sync, then sets `global.__dirname` and `global.__filename` to the `fs-native-extensions` path so code that uses it (e.g. via pear-ipc) in the renderer does not break.
  2. **Renderer API:** Exposes `window.electronAPI` with:
     - Runtime: `getConfig`, `applyUpdate`, `restart`, `checkUpdated`, `onRuntimeUpdating`, `onRuntimeUpdated`
     - Vault: `vaultInvoke(method, args)`, `vaultOnUpdate(cb)`
- The renderer must use this preload; without it there is no `window.electronAPI` and no correct `__dirname`/`__filename` for fs-native-extensions.

---

## 7. Renderer → main → worklet flow

1. Renderer calls e.g. `window.electronAPI.vaultInvoke('someMethod', [arg1, arg2])`.
2. Preload forwards to `ipcRenderer.invoke('vault:invoke', { method, args })`.
3. Main process `ipcMain.handle('vault:invoke', …)` receives it, gets `vaultClient[method]`, deserializes args (e.g. `__base64` → Buffer), calls the method on `vaultClient`.
4. `PearpassVaultClient` sends the call over the pipe to the worklet; worklet runs the vault logic and replies.
5. Main process serializes the result (e.g. Buffer → `__base64`) and returns to the renderer.

---

## 8. Key files reference

| Role                           | File                                                                             |
| ------------------------------ | -------------------------------------------------------------------------------- |
| Main process                   | `electron/main.cjs`                                                              |
| Preload                        | `electron/preload.cjs`                                                           |
| Flatpak path helper            | `electron/flatpak-paths.cjs`                                                     |
| Worklet entry (ESM)            | `node_modules/@tetherto/pearpass-lib-vault-core/src/worklet/app.js`              |
| Worklet bundle (CJS, packaged) | `node_modules/@tetherto/pearpass-lib-vault-core/src/worklet/app.cjs` (generated) |
| Worklet build script           | `scripts/build.worklet.mjs`                                                      |
| Renderer bundle                | `scripts/bundle-renderer.mjs` → `dist/renderer.bundle.js`                        |
| Build pipeline                 | `package.json` scripts: `build`, `dist:*`, `pear:build:*`                        |

---

## 9. Troubleshooting

- **“Cannot use import statement outside a module” in packaged app**  
  Packaged app must run the CJS worklet (`app.cjs`). Ensure `npm run build` runs the worklet build and `getWorkletPath()` returns `.../app.cjs` when `app.isPackaged` is true.

- **“MODULE_NOT_FOUND” for a package when running from DMG /Applications**  
  With `asar: false` this usually means the package was not included in `build.files` or was only a devDependency. Ensure it is a runtime dependency and matched by `build.files`.

- **“ADDON_NOT_FOUND” for a native addon**  
  The worklet bundle must not inline that package. Ensure the module is in the `external` list in `scripts/build.worklet.mjs` (so it is loaded from `node_modules` at runtime) and that the native binary is present in the packaged app.

- **Flatpak build starts but vault/worklet storage init fails**  
  Ensure `electron/main.cjs` still routes both `app.getPath('userData')` and `pear-runtime-legacy-storage` results through `getSandboxSafePath()` from `electron/flatpak-paths.cjs`. Flatpak commonly exposes XDG directories under `~/.var/app/...`, which the worklet rejects unless they are remapped to the approved `~/.config/...` compatibility location.

- **OTA update appears stuck on Windows**  
  Confirm that the Pear drive for `by-arch/win32-x64/app/...` contains a valid `.msix` (if `PearRuntime` is using `MSIXManager.addPackage`) and that the filename matches the `name` passed to `PearRuntime` in `electron/main.cjs`.


================================================
FILE: e2e/.gitignore
================================================
test-artifacts/
node_modules/
playwright-report/
.DS_Store
.env


================================================
FILE: e2e/components/CreateOrEditPage.js
================================================
import { test, expect } from '../fixtures/app.runner.js'

class CreateOrEditPage {
  constructor(root) {
    this.root = root
  }

  // --- Input fields ---

  getCreateOrEditInputField(field) {
    const overrides = {
      website: 'createoredit-input-website-v2-0',
      attachment: 'createoredit-attachment-upload-v2',
    }
    const dashIndex = field.indexOf('-')
    const testId = overrides[field] ??
      (dashIndex !== -1
        ? `createoredit-${field.slice(0, dashIndex)}-input-${field.slice(dashIndex + 1)}-v2`
        : `createoredit-input-${field}-v2`)
    return this.root.getByTestId(testId).locator('input').first()
  }

  getCreateOrEditTextareaField(field) {
    return this.root.getByTestId(`createoredit-textarea-${field}`)
  }

  async fillCreateOrEditInput(field, value) {
    const input = this.getCreateOrEditInputField(field)
    await input.waitFor({ state: 'visible' })
    await input.fill('')
    await input.fill(value)
  }

  async verifyPasswordToNotHaveValue(password) {
    const passwordInput = this.getCreateOrEditInputField('password')
    await expect(passwordInput).toBeVisible()
    await expect(passwordInput).not.toHaveValue(password)
  }

  // --- Form buttons ---

  getCreateOrEditButton(name) {
    const dashIndex = name.indexOf('-')
    const testId = dashIndex !== -1
      ? `createoredit-${name.slice(0, dashIndex)}-button-${name.slice(dashIndex + 1)}-v2`
      : `createoredit-button-${name}-v2`
    return this.root.getByTestId(testId)
  }

  async clickOnCreateOrEditButton(button) {
    const input = this.getCreateOrEditButton(button)
    await input.waitFor({ state: 'visible' })
    await input.click()
  }

  get saveButton() {
    return this.root.getByTestId('createoredit-button-save')
  }

  get elementItemCloseButton() {
    return this.root.getByTestId(/-close-v2$/).first()
  }

  async clickElementItemCloseButton() {
    await expect(this.elementItemCloseButton).toBeVisible()
    await this.elementItemCloseButton.click()
  }

  // --- Multi-slot website / comment ---

  get detailsWebsite() {
    return this.root.getByTestId('website-multi-slot-input-slot-0')
  }

  async verifyDetailsWebsiteCount(expectedCount) {
    await expect(this.detailsWebsite).toHaveCount(expectedCount)
  }

  get detailsComment() {
    return this.root.getByTestId('comments-multi-slot-input-slot-0')
  }

  async verifyDetailsCommentCount(expectedCount) {
    await expect(this.detailsComment).toHaveCount(expectedCount)
  }

  // --- Password generation ---

  get passwordMenu() {
    return this.root.getByTestId('createoredit-button-generatepassword-v2')
  }

  async openPasswordMenu() {
    await expect(this.passwordMenu).toBeVisible()
    await this.passwordMenu.click()
  }

  get insertPasswordButton() {
    return this.root
      .getByTestId('generatepassword-button-primary-v2')
      .first()
  }

  async clickInsertPasswordButton() {
    await expect(this.insertPasswordButton).toBeVisible()
    await this.insertPasswordButton.click()
  }

  // --- Password field ---

  get elementItemPassword() {
    return this.root.getByPlaceholder('Password')
  }

  get passwordInput() {
    return this.root.getByTestId('createoredit-input-password')
  }

  get elementItemPasswordShowHideFirst() {
    return this.root
      .getByTestId('password-field-eye-button')
      .first()
  }

  get elementItemPasswordShowHideLast() {
    return this.root.getByTestId('password-field-eye-button').last()
  }

  async clickShowHidePasswordButtonFirst() {
    await expect(this.elementItemPasswordShowHideFirst).toBeVisible()
    await this.elementItemPasswordShowHideFirst.click()
  }

  async clickShowHidePasswordButtonLast() {
    await expect(this.elementItemPasswordShowHideLast).toBeVisible()
    await this.elementItemPasswordShowHideLast.click()
  }

  async verifyPasswordType(password_type) {
    const itemDetail = this.root.getByPlaceholder('Password')
    await expect(itemDetail).toBeVisible()
    await expect(itemDetail).toHaveAttribute('type', password_type)
  }

  // --- Attachment upload ---

  getCreateOrEditUploadAttachment() {
    return this.root.getByTestId(/-attachment-upload-v2$/).first()
  }

  async clickOnAttachment() {
    const input = this.getCreateOrEditUploadAttachment()
    await input.waitFor({ state: 'visible' })
    await input.click()
  }

  get deleteAttachmentButton() {
    return this.root.getByTestId(/-button-deleteattachment-v2-0$/).first()
  }

  async clickOnDeleteAttachmentButton() {
    const deleteButton = this.deleteAttachmentButton
    await expect(deleteButton).toBeVisible()
    await deleteButton.click()
  }

  get loadFile() {
    return this.root.getByTestId('createoredit-button-loadfile')
  }

  get fileInput() {
    return this.root.locator('input[type="file"]').first()
  }

  async uploadFile() {
    await this.fileInput.setInputFiles('test-files/TestPhoto.png')
  }

  get uploadedFileLink() {
    return this.root
      .getByTestId('uploadfiles-field-v2')
      .getByText('TestPhoto.png', { exact: true })
  }

  get uploadedFile() {
    return this.root.getByTestId('uploadfiles-button-additem-v2')
  }

  get uploadedImage() {
    return this.root.getByAltText('TestPhoto.png')
  }

  async clickOnUploadedFile() {
    await expect(this.uploadedFile).toBeVisible()
    await this.uploadedFile.click()
  }

  async verifyUploadedFileIsVisible() {
    await expect(this.uploadedFileLink).toBeVisible()
    await expect(this.uploadedFileLink).toHaveText('TestPhoto.png')
  }

  async verifyUploadedImageIsVisible() {
    await expect(this.uploadedImage).toBeVisible()
  }

  // --- Custom note fields ---

  get createCustomNote() {
    return this.root.getByTestId('createcustomfield-option-note')
  }

  get customNoteInput() {
    return this.root.getByTestId('createoredit-custom-input-customfield-v2-0').locator('input').first()
  }

  get customNoteInput_first() {
    return this.root.getByTestId(/^createoredit-custom-input-customfield-v2-/)
  }

  async fillCustomNoteInput() {
    const input = this.customNoteInput
    await input.waitFor({ state: 'visible' })
    await input.fill('')
    await input.fill('Custom Note')
  }

  async fillCustomNoteInput_first() {
    const input = this.customNoteInput_first
    await input.waitFor({ state: 'visible' })
    await input.fill('')
    await input.fill('Custom Note')
  }

  async deleteCustomNote() {
    const input = this.customNoteInput
    await input.waitFor({ state: 'visible' })
    await input.fill('')
  }

  // --- Folder dropdown ---

  get dropdownFolderMenu() {
    return this.root.getByTestId('createoredit-select-folder-v2')
  }

  async openDropdownMenu() {
    await this.dropdownFolderMenu.waitFor({ state: 'attached' })
    await this.dropdownFolderMenu.click()
  }

  async selectFromDropdownMenu(foldername) {
    const folder = this.root.getByTestId(`createoredit-folder-option-v2-${foldername}`)
    await expect(folder).toBeVisible()
    await folder.click()
  }

  // --- Identity sections ---

  getSection(sectionname) {
    return this.root.getByTestId(`createoredit-section-${sectionname}`)
  }

  get identitySection() {
    return this.root.getByTestId(`createoredit-section-personalinfo`)
  }

  async clickOnIdentitySection(sectionname) {
    const section = this.getSection(sectionname)
    await section.waitFor({ state: 'visible' })
    await section.click()
  }

  // --- PassPhrase ---

  get passPhrasePasteButton() {
    return this.root.getByRole('button', { name: 'Paste recovery phrase' }).first()
  }

  async clickOnPasteFromClipboard() {
    const pasteButton = this.passPhrasePasteButton
    await expect(pasteButton).toBeVisible()
    await pasteButton.click()
  }

  // --- Item details (shared verifications) ---

  async verifyItemDetailsValue(labelOrPlaceholder, expectedValue) {
    const itemDetail = this.getElementItemDetails(labelOrPlaceholder)
    await expect(itemDetail).toHaveValue(expectedValue)
  }

  async verifyItemDetailsValueIsNotVisible(labelOrPlaceholder) {
    const itemDetail = this.getElementItemDetails(labelOrPlaceholder)
    await expect(itemDetail).not.toBeVisible()
  }

}

module.exports = { CreateOrEditPage }


================================================
FILE: e2e/components/DetailsPage.js
================================================
import { test, expect } from '../fixtures/app.runner.js'

class DetailsPage {
  constructor(root) {
    this.root = root
  }

  // --- General / counter ---

  get itemDetailsCounter() {
    return this.root
    .getByTestId('details-header-v2')
    .locator('input[placeholder]')
  }

  async verifyDetailsNoItems() {
    await expect(this.itemDetailsCounter).toHaveCount(0)
  }

  // --- Title ---

  get getItemDetailsTitle() {
    return this.root.locator('[data-testid^="details-title"], [data-testid="details-header-v2"]')
  }

  async verifyTitle(expectedTitle) {
    await expect(this.getItemDetailsTitle).toContainText(expectedTitle)
  }

  // --- Item details / multi-slot ---

  getElementItemDetails(labelOrPlaceholder) {
    const v2LabelMap = {
      'Email or username': 'credentials-multi-slot-input-slot-0',
      'Password': 'credentials-multi-slot-input-slot-1',
      'https://': 'website-multi-slot-input-slot-0',
      'Name on card': 'card-details-multi-slot-input-slot-0',
      'Number on card': 'card-details-multi-slot-input-slot-1',
      'Date of expire': 'card-details-multi-slot-input-slot-2',
      'Security code': 'card-details-multi-slot-input-slot-3',
      'Pin code': 'card-details-multi-slot-input-slot-4',
      'Comment': 'comments-multi-slot-input-slot-0',
      'Wi-Fi Password': 'credentials-multi-slot-input-slot-0',
      'Add comment': 'comments-multi-slot-input-slot-0',
      'Other Field': 'custom-fields-multi-slot-input-slot-0',
    }
    const v2TestId = v2LabelMap[labelOrPlaceholder]
    if (v2TestId) {
      return this.root.getByTestId(v2TestId).locator('input').first()
    }

    return this.root
      .locator('input', {
        has: this.root.locator('[data-testid="details-header"]', {
          hasText: labelOrPlaceholder
        })
      })
      .or(this.root.locator(`input[placeholder="${labelOrPlaceholder}"]`))
  }

  async verifyItemDetailsValue(labelOrPlaceholder, expectedValue) {
    const itemDetail = this.getElementItemDetails(labelOrPlaceholder)
    await expect(itemDetail).toHaveValue(expectedValue)
  }

  async verifyItemDetailsValueIsNotVisible(labelOrPlaceholder) {
    const itemDetail = this.getElementItemDetails(labelOrPlaceholder)
    await expect(itemDetail).not.toBeVisible()
  }

  // --- Comment / note ---

  get getItemDetailsCommentInput() {
    return this.root.getByTestId('comments-multi-slot-input-slot-0').locator('input')
  }

  async verifyCustomNoteText(expectedText) {
    await expect(this.getItemDetailsCommentInput).toBeVisible()
    await expect(this.getItemDetailsCommentInput).toHaveValue(expectedText)
  }

  getElementItemDetailsNew() {
    return this.root.getByTestId('note-multi-slot-input-slot-0').locator('input').first()
  }

  async verifyNoteText(note_text) {
    const noteTextDetail = this.getElementItemDetailsNew()
    await expect(noteTextDetail).toBeVisible()
    await expect(noteTextDetail).toHaveValue(note_text)
  }

  // --- Identity ---

  getIdentityDetails(name) {
    const v2SlotMap = {
      fullname:               'personal-information-multi-slot-input-slot-0',
      email:                  'personal-information-multi-slot-input-slot-1',
      phone:                  'personal-information-multi-slot-input-slot-2',
      address:                'address-multi-slot-input-slot-0',
      zip:                    'address-multi-slot-input-slot-1',
      city:                   'address-multi-slot-input-slot-2',
      region:                 'address-multi-slot-input-slot-3',
      country:                'address-multi-slot-input-slot-4',
      passportfullname:       'passport-multi-slot-input-slot-0',
      passportnumber:         'passport-multi-slot-input-slot-1',
      passportissuingcountry: 'passport-multi-slot-input-slot-2',
      passportdateofissue:    'passport-multi-slot-input-slot-3',
      passportexpirydate:     'passport-multi-slot-input-slot-4',
      passportnationality:    'passport-multi-slot-input-slot-5',
      passportdob:            'passport-multi-slot-input-slot-6',
      passportgender:         'passport-multi-slot-input-slot-7',
      idcardnumber:           'identity-card-multi-slot-input-slot-0',
      idcarddateofissue:      'identity-card-multi-slot-input-slot-1',
      idcardexpirydate:       'identity-card-multi-slot-input-slot-2',
      idcardissuingcountry:   'identity-card-multi-slot-input-slot-3',
      comment:                'comments-multi-slot-input-slot-0',
      note:                   'comments-multi-slot-input-slot-0',
    }
    const v2TestId = v2SlotMap[name]
    if (v2TestId) {
      return this.root.getByTestId(v2TestId).locator('input').first()
    }
    return this.root.getByTestId(`identitydetails-field-${name}`)
  }

  async verifyIdentityDetails(name) {
    const identityDetail = this.getIdentityDetails(name)
    await expect(identityDetail).toBeVisible()
  }

  async verifyIdentityDetailsValue(name, expectedValue) {
    const identityDetail = this.getIdentityDetails(name)
    await expect(identityDetail).toHaveValue(expectedValue)
  }

  // --- Recovery phrase ---

  get recoveryPhraseDetails() {
    return this.root.getByTestId(/passphrase-word-input-\d+/)
  }

  async verifyAllRecoveryPhraseWords(expectedWords) {
    const slots = this.recoveryPhraseDetails
    const count = await slots.count()
    for (let i = 0; i < count; i++) {
      const input = slots.nth(i).locator('input').first()
      await expect(input).toHaveValue(expectedWords[i])
    }
  }

  // --- Attachment / file ---

  get elementItemFileLink() {
    return this.root
      .getByTestId('attachment-field-0')
      .getByText('TestPhoto.png', { exact: true })
  }

  get uploadedImage() {
    return this.root.getByAltText('TestPhoto.png')
  }

  async clickOnUploadedFile() {
    await expect(this.elementItemFileLink).toBeVisible()
    await this.elementItemFileLink.click()
  }

  async verifyUploadedFileIsVisible() {
    await expect(this.elementItemFileLink).toBeVisible()
  }

  async verifyUploadedImageIsVisible() {
    await expect(this.uploadedImage).toBeVisible()
  }

  // --- Actions bar ---

  get detailsBarActionsButton() {
    return this.root.getByTestId('details-button-actions-v2')
  }

  get detailsBarEditButton() {
    return this.root.getByTestId('details-actions-item-edit-v2')
  }

  get detailsBarFavoriteButton() {
    return this.root.getByTestId('details-actions-item-favorite-v2')
  }

  get detailsBarThreeDots() {
    return this.root.getByTestId('button-round-icon').first()
  }

  async openItemBarThreeDotsDropdownMenu() {
    await expect(this.detailsBarActionsButton).toBeVisible()
    await this.detailsBarActionsButton.click()
  }

  async editElement() {
    await expect(this.detailsBarActionsButton).toBeVisible()
    await this.detailsBarActionsButton.click()
    await expect(this.detailsBarEditButton).toBeVisible()
    await this.detailsBarEditButton.click()
  }

  get markAsFavoriteButton() {
    return this.root.locator('[data-testid="details-actions-item-favorite-v2"]').getByText('Add to Favorites', { exact: true })
  }

  get removeFromFavoritesButton() {
    return this.root.locator('[data-testid="details-actions-item-favorite-v2"]').getByText('Remove from Favorites', { exact: true })
  }

  async clickMarkAsFavoriteButton() {
    await expect(this.detailsBarFavoriteButton).toBeVisible()
    await this.detailsBarFavoriteButton.click()
  }

  async clickRemoveFromFavoritesButton() {
    await expect(this.removeFromFavoritesButton).toBeVisible()
    await this.removeFromFavoritesButton.click()
  }

  async clickFavoriteButton() {
    await expect(this.detailsBarActionsButton).toBeVisible()
    await this.detailsBarActionsButton.click()
    await expect(this.detailsBarFavoriteButton).toBeVisible()
    await this.detailsBarFavoriteButton.click()
  }

  // --- Close button ---

  get elementItemCloseButton() {
    return this.root.getByTestId(/-close-v2$/).first()
  }

  async clickElementItemCloseButton() {
    await expect(this.elementItemCloseButton).toBeVisible()
    await this.elementItemCloseButton.click()
  }

  // --- Folder management ---

  getCreateNewFolderTitleInput() {
    return this.root.locator(
      'input[placeholder="Enter Name"]'
    )
  }

  get createFolderButton() {
    return this.root.getByRole('button', { name: 'Create New Folder' });
  }

  async fillCreateNewFolderTitleInput(value) {
    await this.getCreateNewFolderTitleInput().fill(value)
  }

  async clickCreateFolderButton() {
    const saveBtn = this.root.getByTestId('createfolder-save-v2')
    await expect(saveBtn).toBeVisible()
    await saveBtn.click()
  }

  getItemDetailsFolderName(foldername) {
    return this.root.getByTestId(`sidebar-folder-${foldername}`)
  }

  async verifyItemDetailsFolderName(foldername) {
    const itemDetailsFolder = this.getItemDetailsFolderName(foldername)
    await expect(itemDetailsFolder).toBeVisible()
  }

  // --- Record list / favorites ---

  get recordListContainer() {
    return this.root.getByTestId('recordList-record-container')
  }

  // --- Password visibility ---

  async clickShowHidePasswordButton() {
    await expect(this.elementItemPasswordShowHide).toBeVisible()
    await this.elementItemPasswordShowHide.click()
  }

  async clickPasswordToggle(slotTestId) {
    const toggle = this.root.getByTestId(slotTestId).getByTestId('password-field-eye-button')
    await expect(toggle).toBeVisible()
    await toggle.click()
  }

  async verifyPasswordFieldType(slotTestId, expectedType) {
    const input = this.root.getByTestId(slotTestId).locator('input').first()
    await expect(input).toBeVisible()
    await expect(input).toHaveAttribute('type', expectedType)
  }

}

module.exports = { DetailsPage }


================================================
FILE: e2e/components/LoginPage.js
================================================
import { test, expect } from '../fixtures/app.runner.js'

class LoginPage {
  constructor(root) {
    this.root = root
  }

  // --- Title / ready state ---

  get title() {
    return this.root.locator('h1', { hasText: 'Enter Your Master Password' })
  }

  async waitForReady(timeout = 30000) {
    await expect(this.title).toBeVisible({ timeout })
  }

  // --- Password ---

  get passwordInput() {
    return this.root.getByTestId('login-password-input-v2').locator('input')
  }

  async enterPassword(password) {
    await expect(this.passwordInput).toBeVisible()
    await this.passwordInput.fill(password)
  }

  // --- Continue button ---

  get continueButton() {
    return this.root.getByTestId('login-continue-button-v2')
  }

  async clickContinue() {
    await this.continueButton.click()
  }

  async loginToApplication(password) {
    await this.waitForReady()
    await this.enterPassword(password)
    await this.clickContinue()
  }
}

module.exports = { LoginPage }


================================================
FILE: e2e/components/MainPage.js
================================================
import { test, expect } from '../fixtures/app.runner.js'

class MainPage {
  constructor(root) {
    this.root = root
  }

  // --- Element list / records ---

  get element() {
    return this.root.locator('[data-record-id]').first()
  }

  getElementByPosition(position) {
    return this.root
      .locator('[data-record-id]')
      .nth(position)
      .locator('span')
      .last()
  }

  async clickOnFirstElement() {
    await expect(this.element).toBeVisible()
    await this.element.click()
  }

  async openElementDetails() {
    await expect(this.element).toBeVisible()
    await this.element.click()
  }

  async verifyElementTitle(title) {
    const row = this.root.locator('[data-record-id]').filter({ hasText: title })
    await expect(row).toBeVisible()
  }

  async verifyElementIsNotVisible() {
    await expect(this.element).not.toBeVisible()
  }

  async verifyElementByPosition(position, element_name) {
    await expect(this.getElementByPosition(position)).toHaveText(element_name)
  }

  async clickElementByPosition(position, element_name) {
    const element = this.getElementByPosition(position)
    await expect(element).toContainText(element_name)
    await element.click()
  }

  async elementCheckBox(expectedState) {
    const checkbox = this.element
      .locator('button[aria-checked]')

    await expect(checkbox)
      .toHaveAttribute('aria-checked', String(expectedState))
  }

  // --- Favorites ---

  get mainViewFavoriteIcon() {
    return this.root.getByTestId('multi-select-favorite')
  }

  async clickOnMainViewFavoriteIcon() {
    await expect(this.mainViewFavoriteIcon).toBeVisible()
    await this.mainViewFavoriteIcon.click()
  }

  async favoriteIconDisabled() {
    const favorite1 = this.mainViewFavoriteIcon
    await expect(favorite1).toHaveAttribute('aria-label', 'Add to Favorites')
  }

  async favoriteIconEnabled() {
    const favorite2 = this.mainViewFavoriteIcon
    await expect(favorite2).toHaveAttribute('aria-label', 'Remove from Favorites')
  }

  // --- Multi-select ---

  get mainViewHeaderSelect() {
    return this.root.getByTestId('main-view-header-select')
  }

  get multipleSelectionButon() {
    return this.root.getByTestId('main-view-header-select')
  }

  get multipleSelectDeleteButon() {
    return this.root.getByTestId('multi-select-delete')
  }

  get multipleSelectMoveButon() {
    return this.root.getByTestId('multi-select-move')
  }

  get multipleSelectCancelButon() {
    return this.root.getByTestId('multi-select-cancel-button')
  }

  get multipleSelectCheckerByPosition() {
    return this.root
      .getByTestId('recordList-record-container')
      .nth(`${position}`)
      .getByTestId('undefined-selected')
  }

  async clickMainViewHeaderSelect() {
    await expect(this.mainViewHeaderSelect).toBeVisible()
    await this.mainViewHeaderSelect.click()
  }

  async clickMultipleSelectiontButton() {
    await expect(this.multipleSelectionButon).toBeVisible()
    await this.multipleSelectionButon.click()
  }

  async clickMultipleSelectDeletetButton() {
    await expect(this.multipleSelectDeleteButon).toBeVisible()
    await this.multipleSelectDeleteButon.click()
  }

  async clickMultipleSelectMoveButon() {
    await expect(this.multipleSelectMoveButon).toBeVisible()
    await this.multipleSelectMoveButon.click()
  }

  async verifyMultipleSelectDeleteButtonIsEnabled() {
    await expect(this.multipleSelectDeleteButon).toBeVisible()
    await expect(this.multipleSelectDeleteButon).toBeEnabled()
  }

  // --- Add item / plus button ---

  get mainPlusButon() {
    return this.root.getByTestId('main-plus-button')
  }

  async clickAddItem(type) {
    await expect(this.mainPlusButon).toBeVisible()
    await this.mainPlusButon.click()

    const menuItem = this.root.getByTestId(`add-item-${type}`)
    await expect(menuItem).toBeVisible()
    await menuItem.click()
  }

  // --- Sort ---

  get sortButon() {
    return this.root.getByTestId('main-view-header-sort-menu')
  }

  getSortOption(option) {
    return this.root.getByTestId(`main-view-header-sort-${option}`)
  }

  async clickSortButton() {
    await expect(this.sortButon).toBeVisible()
    await this.sortButon.click()
  }

  async selectSortOption(option) {
    const sortOption = this.getSortOption(option)
    await sortOption.click()
  }

  // --- Folder management ---

  get createNewFolderinputFolderName() {
    return this.root.getByTestId('input-field')
  }

  get createFolderModalButton() {
    return this.root.getByRole('button', { name: 'Create folder' })
  }

  getCollectionButton(button_name) {
    return this.root.getByTestId(`emptycollection-button-create-${button_name}`)
  }

  async verifyElementFolderName(elementfoldername) {
    const folderBtn = this.root.getByTestId(`sidebar-folder-${elementfoldername}`)
    await expect(folderBtn).toBeVisible()
    await folderBtn.click()
    await expect(this.root.locator('[data-record-id]').first()).toBeVisible()
  }

  // --- Move folder ---

  async clickMoveFolderChip(folderName) {
    const chip = this.root.getByTestId(`movefolder-chip-${folderName}`)
    await expect(chip).toBeVisible()
    await chip.click()
  }

  async clickMoveFolderSubmit() {
    const submitBtn = this.root.getByTestId('movefolder-submit-v2')
    await expect(submitBtn).toBeVisible()
    await expect(submitBtn).toBeEnabled()
    await submitBtn.click()
  }

  // --- Empty collection ---

  get emptyCollectionView() {
    return this.root.getByTestId('empty-collection-v2')
  }

  async verifyEmptyCollection() {
    await expect(this.emptyCollectionView).toBeVisible()
  }

  // --- Details close ---

  get detailsCloseButton() {
    return this.root.getByTestId('details-button-collapse')
  }

  async clickDetailsCloseButton() {
    const collapseBtn = this.root.getByTestId('details-button-collapse')
    const modalCloseBtn = this.root
      .getByTestId('modalheader-button-close')
      .last()
    const closeBtn = collapseBtn.or(modalCloseBtn)
    await closeBtn.click({ timeout: 5000 }).catch(() => { })
  }

  // --- Confirm / delete ---

  async clickYesButton() {
    await this.root.getByTestId('delete-records-submit-v2').click()
  }

}

module.exports = { MainPage }


================================================
FILE: e2e/components/SettingsPage.js
================================================
import { test, expect } from '../fixtures/app.runner.js'

class SettingsPage {
  constructor(root) {
    this.root = root
  }

  // --- Navigation ---

  getSettingsDropdownSection(section_name) {
    return this.root.getByTestId(`section-${section_name}`)
  }

  async verifySettingsDropdownSectionIsVisible(section_name) {
    const section_dropdown = this.getSettingsDropdownSection(section_name)
    await expect(section_dropdown).toBeVisible()
  }

  getSettingsDropdownNavigation(section_navigation_name) {
    return this.root.getByTestId(`settings-nav-${section_navigation_name}`)
  }

  async verifySettingsDropdownNavigationIsVisible(section_navigation_name) {
    const section_navigation = this.getSettingsDropdownNavigation(section_navigation_name)
    await expect(section_navigation).toBeVisible()
  }

  get backSettingsButton() {
    return this.root.getByRole('button', { name: 'Go back' });
  }

  async clickBackSettingsButton() {
    await this.backSettingsButton.waitFor({ state: 'visible' });
    await this.backSettingsButton.click();
  }

  // --- Dropdowns ---

  getPearPassFunctionDropdown(pearpass_dropdown_id) {
    return this.root.getByTestId(`settings-${pearpass_dropdown_id}`)
  }

  async clickPearPassFunctionDropdown(dropdown_id) {
    const function_dropdown = this.getPearPassFunctionDropdown(dropdown_id)
    await expect(function_dropdown).toBeVisible()
    await function_dropdown.click()
  }

  getPearPassFunctionDropdownOption(dropdown_option) {
    return this.root.getByTestId(`settings-auto-lock-option-${dropdown_option}`)
  }

  async verifyPearPassFunctionDropdownOptionIsVisible(dropdown_id) {
    const function_dropdown =
      this.getPearPassFunctionDropdownOption(dropdown_id)
    await expect(function_dropdown).toBeVisible()
  }
}

module.exports = { SettingsPage }


================================================
FILE: e2e/components/SideMenuPage.js
================================================
import { test, expect } from '../fixtures/app.runner.js'

class SideMenuPage {
  constructor(root) {
    this.root = root
  }

  // --- Exit / lock ---

  get sidebarExitButton() {
    return this.root.getByTestId('sidebar-lock-app')
  }

  async clickSidebarExitButton() {
    await expect(this.sidebarExitButton).toBeVisible()
    await this.sidebarExitButton.click()
  }

  // --- Settings ---

  get sidebarSettingsButton() {
    return this.root.getByTestId('sidebar-settings-button')
  }

  async clickSidebarSettingsButton() {
    await expect(this.sidebarSettingsButton).toBeVisible()
    await this.sidebarSettingsButton.click()
  }

  // --- Categories ---

  getSidebarCategory(categoryname) {
    return this.root.getByTestId(`sidebar-category-${categoryname}`)
  }

  async selectSideBarCategory(name) {
    const category = this.getSidebarCategory(name)
    await expect(category).toBeVisible()
    await expect(category).toBeEnabled()
    await category.click()
  }

  // --- Favorites folder ---

  get favoritesFolder() {
    return this.root.getByTestId('sidebar-folder-favorites').locator('span').last()
  }

  async verifySideBarFavoritesFolder(items) {
    await expect(this.favoritesFolder).toBeVisible()
    await expect(this.favoritesFolder).toHaveAttribute('aria-label', items)
  }

  // --- Folders ---

  getSideMenuFolder(folderName) {
    return this.root.getByRole('button', { name: folderName })
      .and(this.root.locator('[data-testid^="sidebar-"]'))
  }

  get sidebarAddButton() {
    return this.root.getByTestId('sidebar-folder-add')
  }

  get confirmButton() {
    return this.root.getByTestId('button-primary')
  }

  get deleteFolderButton() {
    return this.root.getByTestId('deletefolder-submit-v2')
  }

  async clickSidebarAddButton() {
    await expect(this.sidebarAddButton).toBeVisible()
    await this.sidebarAddButton.click()
  }

  async createFolder(name) {
    await this.clickSidebarAddButton()
    const nameInput = this.root.getByTestId('createfolder-name-v2').locator('input')
    await expect(nameInput).toBeVisible()
    await nameInput.fill(name)
    const saveBtn = this.root.getByTestId('createfolder-save-v2')
    await expect(saveBtn).toBeVisible()
    await saveBtn.click()
    await expect(saveBtn).toBeHidden()
  }

  async openSideBarFolder(foldername) {
    await expect(this.getSideMenuFolder(foldername)).toBeVisible()
    await this.getSideMenuFolder(foldername).click()
  }

  async deleteMultipleItemsFolder(foldername) {
    const folder = this.getSideMenuFolder(foldername)
    await expect(folder).toBeVisible()
    await folder.click({ button: 'right' })
    const deleteButton = this.root.getByText('Delete Folder', { exact: true })
    await expect(deleteButton).toBeVisible()
    await deleteButton.click()
  }

  async deleteFolder(foldername) {
    const folder = this.getSideMenuFolder(foldername)
    await expect(folder).toBeVisible()
    await folder.click({ button: 'right' })
    const deleteButton = this.root.getByText('Delete Folder', { exact: true })
    await expect(deleteButton).toBeVisible()
    await deleteButton.click()
    await expect(deleteButton).toBeHidden()
  }

  async clickDeleteFolderButton() {
    await expect(this.deleteFolderButton).toBeVisible()
    await this.deleteFolderButton.click()
  }

  async verifySidebarFolderName(foldername) {
    const folder = this.getSideMenuFolder(foldername)
    await expect(folder).toBeVisible()
  }
}

module.exports = { SideMenuPage }


================================================
FILE: e2e/components/Utilities.js
================================================
import { test, expect } from '../fixtures/app.runner.js'

class Utilities {
  constructor(root) {
    this.root = root
  }

  // --- Bulk delete ---

  async deleteAllElements() {
    const emptyState = this.root.getByTestId('empty-collection-v2')

    while (true) {
      const emptyVisible = await emptyState.isVisible().catch(() => false)
      if (emptyVisible) break

      const firstRow = this.root.locator('[data-record-id]').first()
      const rowVisible = await firstRow
        .isVisible({ timeout: 3000 })
        .catch(() => false)
      if (!rowVisible) break

      const recordId = await firstRow.getAttribute('data-record-id')
      if (!recordId) break

      await firstRow.click({ button: 'right' })

      const deleteButton = this.root.getByTestId(
        `record-row-menu-delete-${recordId}`
      )
      await expect(deleteButton).toBeVisible({ timeout: 5000 })
      await deleteButton.click()

      const confirmButton = this.root.getByTestId('delete-records-submit-v2')
      await expect(confirmButton).toBeVisible({ timeout: 5000 })
      await confirmButton.click()

      await confirmButton
        .waitFor({ state: 'hidden', timeout: 5000 })
        .catch(() => { })
    }
  }

  // --- Clipboard ---

  async pasteFromClipboard(locator, text) {
    await this.root.page().evaluate(async (t) => {
      await navigator.clipboard.writeText(t)
    }, text)

    await locator.click()
    const modifier = process.platform === 'darwin' ? 'Meta' : 'Control'
    await this.root.page().keyboard.press(`${modifier}+v`)
  }
}

module.exports = { Utilities }


================================================
FILE: e2e/components/index.js
================================================
export { LoginPage } from './LoginPage.js'
export { CreateOrEditPage } from './CreateOrEditPage.js'
export { DetailsPage } from './DetailsPage.js'
export { MainPage } from './MainPage.js'
export { SideMenuPage } from './SideMenuPage.js'
export { Utilities } from './Utilities.js'
export { SettingsPage } from './SettingsPage.js'


================================================
FILE: e2e/fixtures/app.runner.js
================================================
import { spawn } from 'node:child_process'
import { createRequire } from 'node:module'
import os from 'node:os'
import path from 'node:path'

import { test as base, expect, chromium } from '@playwright/test'

const isWindows = os.platform() === 'win32'

/** Real Electron binary path (avoids Windows spawn EINVAL from .cmd without shell). */
function resolveElectronBinary(appDir) {
  const require = createRequire(path.join(appDir, 'package.json'))
  return require('electron')
}

function sleep(ms) {
  return new Promise((resolve) => setTimeout(resolve, ms))
}

async function connectWithRetries(wsEndpoint, maxRetries) {
  // Windows needs more retries with shorter delays
  // Mac works better with exponential backoff
  const retries = maxRetries ?? (isWindows ? 15 : 10)

  for (let attempt = 0; attempt <= retries; attempt++) {
    try {
      // Sleep AFTER first attempt fails, not before
      if (attempt > 0) {
        const delay = isWindows ? 1000 : 2 ** attempt * 1000
        await sleep(delay)
      }

      console.log(
        `[CDP] Attempting connection to ${wsEndpoint} (attempt ${attempt + 1}/${retries + 1})`
      )
      return await chromium.connectOverCDP(wsEndpoint)
    } catch (err) {
      console.log(`[CDP] Connection failed: ${err.message}`)
      if (attempt === retries) throw err
    }
  }
}

async function waitForPage(browser, maxRetries = 60) {
  for (let attempt = 0; attempt <= maxRetries; attempt++) {
    const contexts = browser.contexts()
    for (const context of contexts) {
      const pages = context.pages()
      // Debug: log all page URLs
      if (attempt % 5 === 0) {
        console.log(
          `[Attempt ${attempt}] Available pages:`,
          pages.map((p) => p.url())
        )
      }

      // Electron loads index.html via file:// on all platforms
      const page = pages.find((p) => p.url().includes('index.html'))

      if (page) {
        console.log('[Found] App page:', page.url())
        return page
      }
    }
    await sleep(1000)
  }

  // Last resort: return any page that's not blank
  const contexts = browser.contexts()
  for (const context of contexts) {
    const page = context.pages().find((p) => !p.url().startsWith('about:'))
    if (page) {
      console.log('[Fallback] Using page:', page.url())
      return page
    }
  }

  return null
}

async function launchApp(appDir) {
  const port = Math.floor(Math.random() * (65535 - 10000 + 1)) + 10000

  console.log(
    `[Launch] Starting app on port ${port}, platform: ${os.platform()}`
  )

  const electronBin = resolveElectronBinary(appDir)

  // ELECTRON_RUN_AS_NODE=1 makes Electron behave as plain Node.js, which
  // prevents require('electron') from returning the built-in API (app, BrowserWindow, etc.)
  const env = { ...process.env }
  delete env.ELECTRON_RUN_AS_NODE

  let proc
  if (isWindows) {
    proc = spawn(
      electronBin,
      ['.', `--remote-debugging-port=${port}`, '--no-sandbox'],
      {
        cwd: appDir,
        stdio: 'inherit',
        windowsHide: false,
        env
      }
    )
  } else {
    // Mac/Linux: use detached process so we can kill the process group on teardown
    proc = spawn(
      electronBin,
      ['.', `--remote-debugging-port=${port}`, '--no-sandbox'],
      {
        cwd: appDir,
        stdio: 'inherit',
        detached: true,
        env
      }
    )
    proc.unref()
  }

  // Give the app time to start before trying to connect
  // Electron needs time for startRuntime() (P2P + worklet) before the window is created
  const initialDelay = isWindows ? 5000 : 5000
  console.log(`[Launch] Waiting ${initialDelay}ms for app to initialize...`)
  await sleep(initialDelay)

  const browser = await connectWithRetries(`http://localhost:${port}`)

  // Listen for new pages on all contexts
  for (const context of browser.contexts()) {
    context.on('page', (p) => console.log('[Event] New page created:', p.url()))
  }

  const page = await waitForPage(browser)

  if (!page) {
    // Final debug output
    const allPages = browser
      .contexts()
      .flatMap((c) => c.pages().map((p) => p.url()))
    console.error('[Debug] All available page URLs:', allPages)
    throw new Error('Could not find app page')
  }

  // Wait for page to be fully loaded on all platforms
  console.log('[Launch] Waiting for page to be ready...')
  await page.waitForLoadState('domcontentloaded')

  // Windows needs additional settling time
  if (isWindows) {
    await sleep(2000)
  }

  const app = { proc, browser, page, isWindows }

  /**
   * Returns the current app page. If the page was closed (e.g. app restarted),
   * tries to find the new page from the browser. Use this in beforeEach to
   * avoid "Target page, context or browser has been closed" errors.
   */
  app.getPage = async function getPage() {
    if (this.page && !this.page.isClosed()) {
      return this.page
    }
    const newPage = await waitForPage(this.browser, 10)
    if (newPage) {
      this.page = newPage
      await this.page.waitForLoadState('domcontentloaded')
      if (isWindows) await sleep(500)
      return this.page
    }
    throw new Error('App page was closed and no new page could be found')
  }

  return app
}

import { spawnSync } from 'node:child_process'

export async function teardown({ proc, browser, isWindows }) {
  try {
    if (proc?.pid) {
      console.log(`[Teardown] Killing Electron process PID=${proc.pid} ...`)
      if (isWindows) {
        // Windows: koristi taskkill
        spawnSync('taskkill', ['/PID', String(proc.pid), '/T', '/F'], {
          stdio: 'inherit'
        })
      } else {
        // Mac/Linux: ubij proces grupu (-pid)
        process.kill(-proc.pid, 'SIGKILL')
      }
    }
  } catch (e) {
    console.warn(
      'Electron process already terminated or could not be killed',
      e.message
    )
  }

  // Mali delay da OS oslobodi port i prozore
  await new Promise((r) => setTimeout(r, 500))

  // Close CDP browser connection
  try {
    if (browser) {
      console.log('[Teardown] Closing CDP browser connection...')
      await browser.close()
    }
  } catch (e) {
    console.warn('Browser already closed', e.message)
  }
}

exports.test = base.extend({
  app: [
    async ({}, use) => {
      const appDir = path.resolve(__dirname, '..', '..')
      const app = await launchApp(appDir)
      await use(app)
      await teardown(app)
    },
    { scope: 'worker' }
  ]
})

exports.expect = expect


================================================
FILE: e2e/fixtures/test-data.js
================================================
'use strict'

/**
 * Centralized test data for reuse across test suites
 */
module.exports = {
  // User credentials
  credentials: {
    validPassword: 'Test123!',
    invalidPassword: 'WrongPassword123!'
  },

  // Vault data
  vault: {
    name: 'Test'
  },

  // Timeouts
  timeouts: {
    navigation: 3000,
    action: 3000
  },

  // PassPhrase
  passphrase: {
    text12:
      'word1 word2 word3 word4 word5 word6 word7 word8 word9 word10 word11 word12',
    text24:
      'word1 word2 word3 word4 word5 word6 word7 word8 word9 word10 word11 word12 word13 word14 word15 word16 word17 word18 word19 word20 word21 word22 word23 word24'
  }
}


================================================
FILE: e2e/package.json
================================================
{
  "name": "pearpass-e2e",
  "version": "1.0.0",
  "scripts": {
    "test": "npx playwright test",
    "test:headed": "npx playwright test --headed",
    "test:debug": "npx playwright test --debug",
    "report": "npx playwright show-report test-artifacts/report"
  },
  "dependencies": {
    "@playwright/test": "^1.52.0",
    "dotenv": "^17.3.1"
  },
  "devDependencies": {
    "clipboardy": "^5.1.0",
    "playwright-qase-reporter": "^2.2.2"
  }
}


================================================
FILE: e2e/playwright.config.js
================================================
import { defineConfig } from '@playwright/test'
import dotenv from 'dotenv'

dotenv.config()

export default defineConfig({
  timeout: 5 * 60 * 1000,
  testDir: './specs',

  workers: 1,
  forbidOnly: !!process.env.CI,
  maxFailures: process.env.CI ? 5 : undefined,
  fullyParallel: false,

  retries: 0,

  use: {
    screenshot: 'only-on-failure',
    trace: 'on-first-retry',
    actionTimeout: 30000,
    navigationTimeout: 60000
  },

  reporter: [
    ['list'],
    ['html', { outputFolder: 'test-artifacts/report', open: 'never' }],
    [
      'playwright-qase-reporter',
      {
        mode: 'testops',
        debug: false,
        testops: {
          api: {
            token: process.env.API_TOKEN
          },
          project: 'PAS',
          uploadAttachments: true,
          run: {
            title: 'Automated Playwright Run',
            description: 'Nightly regression tests',
            complete: true
          },
          batch: {
            size: 100
          }
        },
        framework: {
          browser: {
            addAsParameter: true,
            parameterName: 'Browser'
          },
          markAsFlaky: true
        }
      }
    ]
  ],

  outputDir: 'test-artifacts/results'
})


================================================
FILE: e2e/scripts/explore.js
================================================
'use strict'
const { chromium } = require('@playwright/test')

async function explore() {
  const browser = await chromium.connectOverCDP('http://localhost:9222')
  const page = browser
    .contexts()[0]
    .pages()
    .find((p) => p.url().includes('index.html'))

  console.log('Connected to:', page.url())
  await page.waitForTimeout(2000)

  console.log('\n=== VISIBLE TEXT ===')
  console.log(await page.locator('body').innerText())

  console.log('\n=== BUTTONS ===')
  const buttons = await page.locator('button').all()
  for (const btn of buttons) {
    if (await btn.isVisible()) {
      console.log('-', await btn.textContent())
    }
  }

  console.log('\n=== INPUTS ===')
  const inputs = await page.locator('input').all()
  for (const input of inputs) {
    if (await input.isVisible()) {
      const type = await input.getAttribute('type')
      const placeholder = await input.getAttribute('placeholder')
      console.log(`- type="${type}" placeholder="${placeholder}"`)
    }
  }
}

explore().catch(console.error)


================================================
FILE: e2e/specs/01-Login/creatingLoginItem.test.js
================================================
import { qase } from 'playwright-qase-reporter'

import {
  LoginPage,
  MainPage,
  SideMenuPage,
  CreateOrEditPage,
  Utilities,
  DetailsPage
} from '../../components/index.js'
import { test, expect } from '../../fixtures/app.runner.js'
import testData from '../../fixtures/test-data.js'

test.describe('Creating Login Item', () => {
  test.describe.configure({ mode: 'serial' })

  let loginPage,
    createOrEditPage,
    sideMenuPage,
    mainPage,
    utilities,
    detailsPage,
    page

  test.beforeAll(async ({ app }) => {
    page = await app.getPage()
    const root = page.locator('body')

    loginPage = new LoginPage(root)
    sideMenuPage = new SideMenuPage(root)
    utilities = new Utilities(root)
    mainPage = new MainPage(root)

    await loginPage.loginToApplication(testData.credentials.validPassword)

    await sideMenuPage.selectSideBarCategory('login')
    await utilities.deleteAllElements()
    try {
      await sideMenuPage.deleteFolder('Test Folder')
    } catch (e) {
      // folder may not exist from a previous run
    }
    await mainPage.clickAddItem('login')

    await page.waitForTimeout(testData.timeouts.action)
  })

  test.beforeEach(async ({ app }) => {
    page = await app.getPage()
    const root = page.locator('body')
    loginPage = new LoginPage(root)
    mainPage = new MainPage(root)
    sideMenuPage = new SideMenuPage(root)
    createOrEditPage = new CreateOrEditPage(root)
    utilities = new Utilities(root)
    detailsPage = new DetailsPage(root)
  })

  test.afterAll(async () => {
    await utilities.deleteAllElements()
    try {
      await sideMenuPage.deleteFolder('Test Folder')
    } catch (e) {
    }
    await sideMenuPage.clickSidebarExitButton()
  })

  test('Creating the "Login" item', async () => {
    qase.id(1928)
    await createOrEditPage.fillCreateOrEditInput('title', 'Login Title')
    await createOrEditPage.fillCreateOrEditInput('username', 'Test User')
    await createOrEditPage.fillCreateOrEditInput('password', 'Test Pass')
    await createOrEditPage.fillCreateOrEditInput('website', 'https://www.website.co')
    await createOrEditPage.fillCreateOrEditInput('comment', 'Test Note')
    await createOrEditPage.clickOnCreateOrEditButton('save')
    await page.waitForTimeout(testData.timeouts.action)
  })

  test('Viewing created item. Verify item details', async () => {
    qase.id(1929)
    await mainPage.verifyElementTitle('Login Title')
    await mainPage.openElementDetails()
    await detailsPage.verifyItemDetailsValue('Email or username', 'Test User')
    await detailsPage.verifyItemDetailsValue('Password', 'Test Pass')
    await detailsPage.verifyItemDetailsValue('https://', 'https://www.website.co')
    await detailsPage.verifyCustomNoteText('Test Note')
  })

  test('Password visibility icon displays/hides value', async () => {
    qase.id(1930)
    await mainPage.verifyElementTitle('Login Title')
    await mainPage.openElementDetails()
    await detailsPage.verifyPasswordFieldType('credentials-multi-slot-input-slot-1', 'password')
    await detailsPage.clickPasswordToggle('credentials-multi-slot-input-slot-1')
    await detailsPage.verifyPasswordFieldType('credentials-multi-slot-input-slot-1', 'text')
  })

  test('Dropdown moves to selected item edit screen', async () => {
    qase.id(1931)
    await mainPage.verifyElementTitle('Login Title')
    await sideMenuPage.clickSidebarAddButton()
    await detailsPage.fillCreateNewFolderTitleInput('Test Folder')
    await detailsPage.clickCreateFolderButton()
    await detailsPage.editElement()
    await createOrEditPage.openDropdownMenu()
    await createOrEditPage.selectFromDropdownMenu('Test Folder')
    await createOrEditPage.clickOnCreateOrEditButton('save')
    await expect(detailsPage.getItemDetailsFolderName('Test Folder')).toBeVisible()
    await mainPage.verifyElementFolderName('Test Folder')
  })

  test('Item moved to folder (and cleanup)', async ({ page }) => {
    qase.id(1932)
    await sideMenuPage.verifySidebarFolderName('Test Folder')
    await mainPage.openElementDetails()
    await detailsPage.editElement()
    await createOrEditPage.openDropdownMenu()
    await createOrEditPage.selectFromDropdownMenu('Test Folder')
    await createOrEditPage.clickOnCreateOrEditButton('save')

    await sideMenuPage.deleteFolder('Test Folder')
  })

  test('Add via Favorite icon', async ({ page }) => {
    qase.id(1933)
    await sideMenuPage.selectSideBarCategory('all')
    await mainPage.clickMainViewHeaderSelect()
    await mainPage.elementCheckBox(false)
    await mainPage.clickOnFirstElement()
    await mainPage.elementCheckBox(true)
    await mainPage.clickOnMainViewFavoriteIcon()
    await sideMenuPage.verifySideBarFavoritesFolder('1 items')
  })

  test('Remove via Favorite icon', async ({ page }) => {
    qase.id(1934)
    await mainPage.clickMainViewHeaderSelect()
    await mainPage.clickOnFirstElement()
    await mainPage.clickOnMainViewFavoriteIcon()
    await sideMenuPage.verifySideBarFavoritesFolder('0 items')
  })

  test('Add via More options', async ({ page }) => {
    qase.id(1935)
    await mainPage.openElementDetails()
    await detailsPage.openItemBarThreeDotsDropdownMenu()
    await detailsPage.clickMarkAsFavoriteButton()
    await sideMenuPage.verifySideBarFavoritesFolder('1 items')
  })

  test('Remove via More options', async ({ page }) => {
    qase.id(1936)
    await detailsPage.openItemBarThreeDotsDropdownMenu()
    await detailsPage.clickRemoveFromFavoritesButton()
    await sideMenuPage.verifySideBarFavoritesFolder('0 items')
  })

  // test('Add Custom Note', async ({ page }) => {
  //   qase.id(1937)
  //   await mainPage.verifyElementTitle('Login Title')
  //   await mainPage.openElementDetails()
  //   await detailsPage.editElement()
  //   await createOrEditPage.clickCreateCustomItem()
  //   await createOrEditPage.clickCustomItemOptionNote()
  //   await expect(createOrEditPage.customNoteInput).toHaveCount(1)
  //   await createOrEditPage.fillCustomNoteInput()
  //   await createOrEditPage.clickOnCreateOrEditButton('save')
  //   await page.waitForTimeout(testData.timeouts.action)
  //   await mainPage.clickDetailsCloseButton()
  // })

  // test('Delete Note field', async ({ page }) => {
  //   qase.id(1938)
  //   await mainPage.verifyElementTitle('Login Title')
  //   await mainPage.openElementDetails()
  //   await detailsPage.editElement()
  //   await expect(createOrEditPage.customNoteInput_first).toHaveCount(2)
  //   await createOrEditPage.deleteCustomNote()
  //   await expect(createOrEditPage.customNoteInput_first).toHaveCount(1)
  //   await createOrEditPage.clickOnCreateOrEditButton('save')
  //   await page.waitForTimeout(testData.timeouts.action)
  //   await mainPage.clickDetailsCloseButton()
  // })

  test('Close via Cross icon', async ({ page }) => {
    qase.id(1939)
    await mainPage.verifyElementTitle('Login Title')
    await mainPage.openElementDetails()
    await detailsPage.editElement()
    await detailsPage.clickElementItemCloseButton()
    await mainPage.verifyElementTitle('Login Title')
  })

  test('View uploaded file in Edit mode', async ({ page }) => {
    qase.id(1940)
    await detailsPage.editElement()
    await createOrEditPage.clickOnAttachment()
    await createOrEditPage.uploadFile()
    await createOrEditPage.verifyUploadedFileIsVisible()
    await createOrEditPage.clickOnUploadedFile()

    await createOrEditPage.clickOnCreateOrEditButton('save')
    await page.waitForTimeout(testData.timeouts.action)

    await detailsPage.verifyUploadedFileIsVisible()

    await detailsPage.clickOnUploadedFile()
    await detailsPage.verifyUploadedImageIsVisible()

    await createOrEditPage.clickElementItemCloseButton()
  })

  test('Empty fields not displayed in view mode', async ({ page }) => {
    qase.id(1942)
    await mainPage.verifyElementTitle('Login Title')
    await mainPage.openElementDetails()
    await detailsPage.editElement()
    await createOrEditPage.fillCreateOrEditInput('username', '')
    await createOrEditPage.fillCreateOrEditInput('password', '')
    await createOrEditPage.fillCreateOrEditInput('website', '')
    await createOrEditPage.fillCreateOrEditInput('comment', '')
    await createOrEditPage.clickOnDeleteAttachmentButton()

    await createOrEditPage.clickOnCreateOrEditButton('save')
    await mainPage.openElementDetails()

    await detailsPage.verifyDetailsNoItems()

    await test.step('CLOSE DETAILS', async () => {
      await mainPage.clickDetailsCloseButton()
    })
  })
})


================================================
FILE: e2e/specs/01-Login/creatingLoginItemPassword.test.js
================================================
import { qase } from 'playwright-qase-reporter'

import {
  LoginPage,
  MainPage,
  SideMenuPage,
  CreateOrEditPage,
  Utilities,
  DetailsPage
} from '../../components/index.js'
import { test, expect } from '../../fixtures/app.runner.js'
import testData from '../../fixtures/test-data.js'

test.describe('Password', () => {
  test.describe.configure({ mode: 'serial' })

  let loginPage,
    createOrEditPage,
    sideMenuPage,
    mainPage,
    utilities,
    detailsPage,
    page

  test.beforeAll(async ({ app }) => {
    page = await app.getPage()
    const root = page.locator('body')

    loginPage = new LoginPage(root)
    mainPage = new MainPage(root)
    sideMenuPage = new SideMenuPage(root)
    createOrEditPage = new CreateOrEditPage(root)
    utilities = new Utilities(root)
    detailsPage = new DetailsPage(root)

    await loginPage.loginToApplication(testData.credentials.validPassword)

    await sideMenuPage.selectSideBarCategory('login')
    await utilities.deleteAllElements()
    await mainPage.clickAddItem('login')

    await createOrEditPage.fillCreateOrEditInput('title', 'Login Title')
    await createOrEditPage.fillCreateOrEditInput('username', 'Test User')
    await createOrEditPage.fillCreateOrEditInput('password', 'Test Pass')
    await createOrEditPage.fillCreateOrEditInput('website', 'https://www.website.co')
    await createOrEditPage.fillCreateOrEditInput('comment', 'Test Note')
    await createOrEditPage.clickOnCreateOrEditButton('save')
    await page.waitForTimeout(testData.timeouts.action)
  })

  test.beforeEach(async () => {
    const root = page.locator('body')
    loginPage = new LoginPage(root)
    mainPage = new MainPage(root)
    sideMenuPage = new SideMenuPage(root)
    createOrEditPage = new CreateOrEditPage(root)
    utilities = new Utilities(root)
    detailsPage = new DetailsPage(root)
  })

  test.afterAll(async () => {
    await utilities.deleteAllElements()
    await sideMenuPage.clickSidebarExitButton()
  })

  test('Verify that the password was changed to a generic password, with "Safe" strength as the default option.', async () => {
    qase.id(2000)
    await mainPage.openElementDetails()
    await detailsPage.editElement()
    await createOrEditPage.openPasswordMenu()
    await createOrEditPage.clickInsertPasswordButton()
    await createOrEditPage.clickShowHidePasswordButtonLast()
    await createOrEditPage.verifyPasswordToNotHaveValue('Test Pass')
    await createOrEditPage.clickOnCreateOrEditButton('save')
    await page.waitForTimeout(testData.timeouts.action)
    await mainPage.clickDetailsCloseButton()
  })

  test('Verify that password strength updates when the "special characters" switch is toggled', async () => {
    qase.id(2001)
    const root = page.locator('body')
    await mainPage.openElementDetails()
    await detailsPage.editElement()
    await createOrEditPage.openPasswordMenu()

    await expect(root.getByRole('radio', { name: /Random Characters/i })).toBeChecked()
    await expect(root.getByText('8 chars')).toBeVisible()
    await expect(root.getByText('Special character')).toBeVisible()
    const toggle = root.locator('[role="switch"]')
    await expect(toggle).toHaveAttribute('aria-checked', 'true')
    await expect(root.getByText('Strong')).toBeVisible()

    await toggle.click()
    await expect(toggle).toHaveAttribute('aria-checked', 'false')
    await expect(root.getByText('Strong')).not.toBeVisible()

    await toggle.click()
    await expect(toggle).toHaveAttribute('aria-checked', 'true')
    await expect(root.getByText('Strong')).toBeVisible()

    await root.getByTestId('generatepassword-button-discard-v2').click()
    await createOrEditPage.clickOnCreateOrEditButton('discard')
    await mainPage.clickDetailsCloseButton()
  })
})


================================================
FILE: e2e/specs/01-Login/editingDeletingLoginItem.test.js
================================================
import { qase } from 'playwright-qase-reporter'

import {
  LoginPage,
  MainPage,
  SideMenuPage,
  CreateOrEditPage,
  Utilities,
  DetailsPage
} from '../../components/index.js'
import { test, expect } from '../../fixtures/app.runner.js'
import testData from '../../fixtures/test-data.js'

test.describe('Editing/Deleting Login Item', () => {
  test.describe.configure({ mode: 'serial' })

  let loginPage,
    createOrEditPage,
    sideMenuPage,
    mainPage,
    utilities,
    detailsPage,
    page

  test.beforeAll(async ({ app }) => {
    page = await app.getPage()
    const root = page.locator('body')

    loginPage = new LoginPage(root)
    mainPage = new MainPage(root)
    sideMenuPage = new SideMenuPage(root)
    createOrEditPage = new CreateOrEditPage(root)
    utilities = new Utilities(root)
    detailsPage = new DetailsPage(root)

    await loginPage.loginToApplication(testData.credentials.validPassword)

    await sideMenuPage.selectSideBarCategory('login')
    await utilities.deleteAllElements()
    await mainPage.clickAddItem('login')

    await createOrEditPage.fillCreateOrEditInput('title', 'Login Title')
    await createOrEditPage.fillCreateOrEditInput('username', 'Test User')
    await createOrEditPage.fillCreateOrEditInput('password', 'Test Pass')
    await createOrEditPage.fillCreateOrEditInput('website', 'https://www.website.co')
    await createOrEditPage.fillCreateOrEditInput('comment', 'Test Note')
    await createOrEditPage.clickOnCreateOrEditButton('save')
    await page.waitForTimeout(testData.timeouts.action)

    await page.waitForTimeout(testData.timeouts.action)
  })

  test.beforeEach(async ({ app }) => {
    page = await app.getPage()
    const root = page.locator('body')
    loginPage = new LoginPage(root)
    mainPage = new MainPage(root)
    sideMenuPage = new SideMenuPage(root)
    createOrEditPage = new CreateOrEditPage(root)
    utilities = new Utilities(root)
    detailsPage = new DetailsPage(root)
  })

  test.afterAll(async () => {
    await utilities.deleteAllElements()
    await sideMenuPage.clickSidebarExitButton()
  })

  test('Verify that edited "Login" item fields are saved correctly', async () => {
    qase.id(2034)
    await mainPage.openElementDetails()
    await detailsPage.editElement()
    await createOrEditPage.fillCreateOrEditInput('title', 'Login Title EDITED')
    await createOrEditPage.fillCreateOrEditInput('username', 'Test User EDITED')
    await createOrEditPage.fillCreateOrEditInput('password', 'Test Pass EDITED')
    await createOrEditPage.fillCreateOrEditInput('website', 'https://www.website1.co')
    await createOrEditPage.fillCreateOrEditInput('comment', 'Test Note EDITED')
    await createOrEditPage.clickOnCreateOrEditButton('save')
    await page.waitForTimeout(testData.timeouts.action)

    await mainPage.verifyElementTitle('Login Title EDITED')
    await mainPage.openElementDetails()
    await detailsPage.verifyItemDetailsValue('Email or username', 'Test User EDITED')
    await detailsPage.verifyItemDetailsValue('Password', 'Test Pass EDITED')
    await detailsPage.verifyItemDetailsValue('https://', 'https://www.website1.co')
    await detailsPage.verifyCustomNoteText('Test Note EDITED')
  })

  test('Verify that deleted "Website" and custom "Note" fields are not saved in the edited "Login" item', async () => {
    qase.id(2035)
    await detailsPage.editElement()
    await createOrEditPage.fillCreateOrEditInput('website', '')
    await createOrEditPage.fillCreateOrEditInput('comment', '')

    await createOrEditPage.clickOnCreateOrEditButton('save')

    await detailsPage.verifyItemDetailsValue('Email or username', 'Test User EDITED')
    await detailsPage.verifyItemDetailsValue('Password', 'Test Pass EDITED')

    await createOrEditPage.verifyDetailsWebsiteCount(0)
    await createOrEditPage.verifyDetailsCommentCount(0)
  })

  test('Empty fields are not displayed in view mode', async () => {
    qase.id(2036)
    await detailsPage.editElement()
    await createOrEditPage.fillCreateOrEditInput('username', '')
    await createOrEditPage.fillCreateOrEditInput('password', '')

    await createOrEditPage.clickOnCreateOrEditButton('save')
    await mainPage.openElementDetails()

    await detailsPage.verifyDetailsNoItems()
  })

  test('Verify that the "Login" item is removed after deletion', async () => {
    qase.id(2037)
    await utilities.deleteAllElements()
    await mainPage.verifyElementIsNotVisible()
  })

  test('Verify that the empty collection view is displayed on the Home screen after deleting the last item', async () => {
    qase.id(2037)
    await sideMenuPage.selectSideBarCategory('all')
    await expect(mainPage.emptyCollectionView).toBeVisible()
  })
})


================================================
FILE: e2e/specs/02-CreditCard/creatingCreditCardItem.test.js
================================================
import { qase } from 'playwright-qase-reporter'

import {
  LoginPage,
  MainPage,
  SideMenuPage,
  CreateOrEditPage,
  Utilities,
  DetailsPage
} from '../../components/index.js'
import { test, expect } from '../../fixtures/app.runner.js'
import testData from '../../fixtures/test-data.js'

test.describe('Creating Credit Card Item', async () => {
  test.describe.configure({ mode: 'serial' })

  let loginPage,
    createOrEditPage,
    sideMenuPage,
    mainPage,
    utilities,
    detailsPage,
    page

  test.beforeAll(async ({ app }) => {
    page = await app.getPage()
    const root = page.locator('body')
    loginPage = new LoginPage(root)
    sideMenuPage = new SideMenuPage(root)
    createOrEditPage = new CreateOrEditPage(root)
    utilities = new Utilities(root)
    mainPage = new MainPage(root)
    detailsPage = new DetailsPage(root)

    await loginPage.loginToApplication(testData.credentials.validPassword)

    await sideMenuPage.selectSideBarCategory('all')
    await utilities.deleteAllElements()
    await sideMenuPage.selectSideBarCategory('creditCard')
    await mainPage.clickAddItem('creditCard')

    await page.waitForTimeout(testData.timeouts.action)
  })

  test.beforeEach(async ({ app }) => {
    page = await app.getPage()
    const root = page.locator('body')
    loginPage = new LoginPage(root)
    mainPage = new MainPage(root)
    sideMenuPage = new SideMenuPage(root)
    createOrEditPage = new CreateOrEditPage(root)
    utilities = new Utilities(root)
    detailsPage = new DetailsPage(root)
  })

  test.afterAll(async ({ app }) => {
    await utilities.deleteAllElements()
    await sideMenuPage.clickSidebarExitButton()
  })

  test('Creating the "Credit Card" item', async ({ page }) => {
    qase.id(2115)
    await createOrEditPage.fillCreateOrEditInput('creditcard-title', 'Credit Card Title')
    await createOrEditPage.fillCreateOrEditInput('creditcard-name', 'John')
    await createOrEditPage.fillCreateOrEditInput('creditcard-number', '1231 2312')
    await createOrEditPage.fillCreateOrEditInput('creditcard-expiredate', '12 12')
    await createOrEditPage.fillCreateOrEditInput('creditcard-securitycode', '111')
    await createOrEditPage.fillCreateOrEditInput('creditcard-pincode', '5555')
    await createOrEditPage.fillCreateOrEditInput('creditcard-comment', 'Credit Card Note')
    await createOrEditPage.clickOnCreateOrEditButton('creditcard-save')
  })

  test('Viewing created item. Verify item details', async ({ page }) => {
    qase.id(2116)
    await mainPage.verifyElementTitle('Credit Card Title')
    await mainPage.openElementDetails()

    await detailsPage.verifyItemDetailsValue('Name on card', 'John')
    await detailsPage.verifyItemDetailsValue('Number on card', '1231 2312')
    await detailsPage.verifyItemDetailsValue('Date of expire', '12 12')
    await detailsPage.verifyItemDetailsValue('Security code', '111')
    await detailsPage.verifyItemDetailsValue('Pin code', '5555')
    await detailsPage.verifyItemDetailsValue('Comment', 'Credit Card Note')
  })

  test('Password visibility icon displays/hides value', async ({ page }) => {
    qase.id(2117)
    await mainPage.verifyElementTitle('Credit Card Title')
    await mainPage.openElementDetails()
    await detailsPage.verifyPasswordFieldType('card-details-multi-slot-input-slot-3', 'password')
    await detailsPage.clickPasswordToggle('card-details-multi-slot-input-slot-3')
    await detailsPage.verifyPasswordFieldType('card-details-multi-slot-input-slot-3', 'text')
  })

  test('Add via Favorite icon', async ({ page }) => {
    qase.id(2120)
    await sideMenuPage.selectSideBarCategory('all')
    await mainPage.clickMainViewHeaderSelect()
    await mainPage.elementCheckBox(false)
    await mainPage.clickOnFirstElement()
    await mainPage.elementCheckBox(true)
    await mainPage.clickOnMainViewFavoriteIcon()
    await sideMenuPage.verifySideBarFavoritesFolder('1 items')
  })

  test('Remove via Favorite icon', async ({ page }) => {
    qase.id(2121)
    await mainPage.clickMainViewHeaderSelect()
    await mainPage.clickOnFirstElement()
    await mainPage.clickOnMainViewFavoriteIcon()
    await sideMenuPage.verifySideBarFavoritesFolder('0 items')
  })

  test('Add via More options', async ({ page }) => {
    qase.id(2122)
    await mainPage.openElementDetails()
    await detailsPage.openItemBarThreeDotsDropdownMenu()
    await detailsPage.clickMarkAsFavoriteButton()
    await sideMenuPage.verifySideBarFavoritesFolder('1 items')
  })

  test('Remove via More options', async ({ page }) => {
    qase.id(2123)
    await detailsPage.openItemBarThreeDotsDropdownMenu()
    await detailsPage.clickRemoveFromFavoritesButton()
    await sideMenuPage.verifySideBarFavoritesFolder('0 items')
  })

  // test('Add Custom Note', async ({ page }) => {
  //   qase.id(2124)
  //   await mainPage.verifyElementTitle('Credit Card Title')
  //   await mainPage.openElementDetails()
  //   await detailsPage.editElement()
  //   await createOrEditPage.clickCreateCustomItem()
  //   await createOrEditPage.clickCustomItemOptionNote()
  //   await expect(createOrEditPage.customNoteInput).toHaveCount(1)
  //   await createOrEditPage.fillCustomNoteInput()

  //   await createOrEditPage.clickOnCreateOrEditButton('save')
  //   await page.waitForTimeout(testData.timeouts.action)
  //   await mainPage.clickDetailsCloseButton()
  // })

  // test('Delete Note field', async ({ page }) => {
  //   qase.id(2125)
  //   await mainPage.verifyElementTitle('Credit Card Title')
  //   await mainPage.openElementDetails()
  //   await detailsPage.editElement()
  //   await expect(createOrEditPage.customNoteInput_first).toHaveCount(2)
  //   await createOrEditPage.deleteCustomNote()
  //   await expect(createOrEditPage.customNoteInput_first).toHaveCount(1)
  //   await createOrEditPage.clickOnCreateOrEditButton('save')
  //   await page.waitForTimeout(testData.timeouts.action)
  //   await mainPage.clickDetailsCloseButton()
  // })

  test('Close via Cross icon', async ({ page }) => {
    qase.id(2126)
    await mainPage.verifyElementTitle('Credit Card Title')
    await mainPage.openElementDetails()
    await detailsPage.editElement()
    await detailsPage.clickElementItemCloseButton()
    await mainPage.verifyElementTitle('Credit Card Title')
  })

  test('View uploaded file in Edit mode', async ({ page }) => {
    qase.id(2127)
    await detailsPage.editElement()
    await createOrEditPage.clickOnAttachment()
    await createOrEditPage.uploadFile()
    await createOrEditPage.verifyUploadedFileIsVisible()
    await createOrEditPage.clickOnUploadedFile()

    await createOrEditPage.clickOnCreateOrEditButton('creditcard-save')
    await page.waitForTimeout(testData.timeouts.action)


    await detailsPage.verifyUploadedFileIsVisible()

    await detailsPage.clickOnUploadedFile()
    await detailsPage.verifyUploadedImageIsVisible()

    await createOrEditPage.clickElementItemCloseButton()
  })

  test('Empty fields not displayed in view mode', async ({ page }) => {
    qase.id(2129)
    await mainPage.verifyElementTitle('Credit Card Title')
    await mainPage.openElementDetails()
    await detailsPage.editElement()
    await createOrEditPage.fillCreateOrEditInput('creditcard-name', '')
    await createOrEditPage.fillCreateOrEditInput('creditcard-number', '')
    await createOrEditPage.fillCreateOrEditInput('creditcard-expiredate', '')
    await createOrEditPage.fillCreateOrEditInput('creditcard-securitycode', '')
    await createOrEditPage.fillCreateOrEditInput('creditcard-pincode', '')
    await createOrEditPage.fillCreateOrEditInput('creditcard-comment', '')
    await createOrEditPage.clickOnDeleteAttachmentButton()

    await createOrEditPage.clickOnCreateOrEditButton('creditcard-save')
    await mainPage.openElementDetails()

    await detailsPage.verifyDetailsNoItems()

    await test.step('CLOSE DETAILS', async () => {
      await mainPage.clickDetailsCloseButton()
    })
  })
})


================================================
FILE: e2e/specs/02-CreditCard/editingDeleteCreditCardItem.test.js
================================================
import { qase } from 'playwright-qase-reporter'

import {
  LoginPage,
  MainPage,
  SideMenuPage,
  CreateOrEditPage,
  Utilities,
  DetailsPage
} from '../../components/index.js'
import { test, expect } from '../../fixtures/app.runner.js'
import testData from '../../fixtures/test-data.js'

test.describe('Editing/Deleting Credit Card Item', () => {
  test.describe.configure({ mode: 'serial' })

  let loginPage,
    createOrEditPage,
    sideMenuPage,
    mainPage,
    utilities,
    detailsPage,
    page

  test.beforeAll(async ({ app }) => {
    page = await app.getPage()
    const root = page.locator('body')

    loginPage = new LoginPage(root)
    mainPage = new MainPage(root)
    sideMenuPage = new SideMenuPage(root)
    createOrEditPage = new CreateOrEditPage(root)
    utilities = new Utilities(root)
    detailsPage = new DetailsPage(root)

    await loginPage.loginToApplication(testData.credentials.validPassword)

    await sideMenuPage.selectSideBarCategory('all')
    await utilities.deleteAllElements()
    await sideMenuPage.selectSideBarCategory('creditCard')
    await mainPage.clickAddItem('creditCard')

    await createOrEditPage.fillCreateOrEditInput('creditcard-title', 'Credit Card Title')
    await createOrEditPage.fillCreateOrEditInput('creditcard-name', 'John')
    await createOrEditPage.fillCreateOrEditInput('creditcard-number', '12312312')
    await createOrEditPage.fillCreateOrEditInput('creditcard-expiredate', '1212')
    await createOrEditPage.fillCreateOrEditInput('creditcard-securitycode', '111')
    await createOrEditPage.fillCreateOrEditInput('creditcard-pincode', '111')
    await createOrEditPage.fillCreateOrEditInput('creditcard-comment', 'Credit Card Note')
    await createOrEditPage.clickOnCreateOrEditButton('creditcard-save')
    await page.waitForTimeout(testData.timeouts.action)
    await mainPage.verifyElementTitle('Credit Card Title')

    await page.waitForTimeout(testData.timeouts.action)
  })

  test.beforeEach(async ({ app }) => {
    page = await app.getPage()
    const root = page.locator('body')
    loginPage = new LoginPage(root)
    mainPage = new MainPage(root)
    sideMenuPage = new SideMenuPage(root)
    createOrEditPage = new CreateOrEditPage(root)
    utilities = new Utilities(root)
    detailsPage = new DetailsPage(root)
  })

  test.afterAll(async ({ app }) => {
    await utilities.deleteAllElements()
    await sideMenuPage.clickSidebarExitButton()
  })

  test('Verify that edited "Credit Card" item fields are saved correctly', async () => {
    qase.id(2130)
    await mainPage.openElementDetails()
    await detailsPage.editElement()
    await createOrEditPage.fillCreateOrEditInput('creditcard-name', 'John EDITED')
    await createOrEditPage.fillCreateOrEditInput('creditcard-number', '99999999')
    await createOrEditPage.fillCreateOrEditInput('creditcard-expiredate', '0101')
    await createOrEditPage.fillCreateOrEditInput('creditcard-securitycode', '222')
    await createOrEditPage.fillCreateOrEditInput('creditcard-pinco
Download .txt
gitextract_j12gn_f4/

├── .claude/
│   └── skills/
│       └── use-ui-kit/
│           └── SKILL.md
├── .cursor/
│   └── rules/
│       └── use-ui-kit.mdc
├── .github/
│   ├── actions/
│   │   └── authorize-pr/
│   │       └── action.yml
│   ├── pull_request_template.md
│   └── workflows/
│       ├── ci-pr.yml
│       ├── ci-push.yml
│       └── ci-reusable.yml
├── .gitignore
├── .husky/
│   └── pre-commit
├── .npmrc
├── .nvmrc
├── AGENTS.md
├── CLAUDE.md
├── CONTRIBUTING.md
├── LICENSE.md
├── NOTICE.md
├── README.md
├── SECURITY.md
├── app.electron.tsx
├── appling/
│   ├── README.md
│   ├── app.cjs
│   ├── app.dev.cjs
│   ├── app.staging.cjs
│   ├── entitlements.plist
│   ├── lib/
│   │   ├── install.cjs
│   │   ├── preflight.cjs
│   │   ├── progress.cjs
│   │   ├── utils.cjs
│   │   ├── view.html.cjs
│   │   └── worker.cjs
│   └── package.json
├── assets/
│   ├── animations/
│   │   ├── category.riv
│   │   ├── form_credit_card.riv
│   │   ├── pearpass_password.riv
│   │   └── sync_without_the_cloud_animation.riv
│   ├── fonts/
│   │   └── humbleNostalgia/
│   │       └── HumbleNostalgia.otf
│   ├── rive/
│   │   └── rive_webgl2.wasm
│   └── video/
│       ├── onboarding_lock_loop.webm
│       └── onboarding_lock_start.webm
├── babel.config.cjs
├── babel.strict-dom.cjs
├── build-assets/
│   └── win/
│       └── AppxManifest.xml
├── docs/
│   └── Electron-Packaging-And-Runtime.md
├── e2e/
│   ├── .gitignore
│   ├── components/
│   │   ├── CreateOrEditPage.js
│   │   ├── DetailsPage.js
│   │   ├── LoginPage.js
│   │   ├── MainPage.js
│   │   ├── SettingsPage.js
│   │   ├── SideMenuPage.js
│   │   ├── Utilities.js
│   │   └── index.js
│   ├── fixtures/
│   │   ├── app.runner.js
│   │   └── test-data.js
│   ├── package.json
│   ├── playwright.config.js
│   ├── scripts/
│   │   └── explore.js
│   └── specs/
│       ├── 01-Login/
│       │   ├── creatingLoginItem.test.js
│       │   ├── creatingLoginItemPassword.test.js
│       │   └── editingDeletingLoginItem.test.js
│       ├── 02-CreditCard/
│       │   ├── creatingCreditCardItem.test.js
│       │   └── editingDeleteCreditCardItem.test.js
│       ├── 03-WiFi/
│       │   ├── creatingWiFiItem.test.js
│       │   └── editingDeletingWiFiItem.test.js
│       ├── 04-Identity/
│       │   ├── creatingIdentityItem.test.js
│       │   └── editingDeletingIdentityItem.test.js
│       ├── 05-PassPhrase/
│       │   ├── creatingPassPhraseItem.test.js
│       │   └── editingDeletingPassPhraseItem.test.js
│       ├── 06-Note/
│       │   ├── creatingNoteItem.test.js
│       │   └── editingDeleteNoteItem.test.js
│       ├── 07-CustomField/
│       │   ├── creatingCustomFieldItem.test.js
│       │   └── editingDeleteCustomFieldItem.test.js
│       ├── 08-MultipleSelection/
│       │   └── multipleSelection.test.js
│       ├── 09-SortingAndFiltering/
│       │   └── sorting.test.js
│       └── 10-Settings/
│           └── settings.test.js
├── electron/
│   ├── clipboardCleanup.cjs
│   ├── clipboardCleanup.test.js
│   ├── clipboardCleanup.windows.ps1
│   ├── clipboardCleanupHelper.cjs
│   ├── clipboardCleanupHelper.test.js
│   ├── flatpak-paths.cjs
│   ├── flatpak-paths.test.js
│   ├── linuxWaylandClipboard.cjs
│   ├── linuxWaylandClipboardFallback.cjs
│   ├── linuxX11Clipboard.cjs
│   ├── linuxX11ClipboardFallback.cjs
│   ├── main.cjs
│   ├── preload.cjs
│   ├── preload.test.js
│   └── runtime-config.cjs
├── electron-builder.linux.json
├── electron-builder.mac.json
├── eslint.config.js
├── flatpak/
│   ├── appimage/
│   │   └── .gitkeep
│   ├── com.pears.pass.desktop
│   ├── com.pears.pass.metainfo.xml
│   └── com.pears.pass.yaml
├── forge.config.cjs
├── index.html
├── index.js
├── jest.config.js
├── lingui.config.js
├── package.json
├── resources/
│   └── bin/
│       ├── wl-copy-arm64
│       ├── wl-copy-x86_64
│       ├── wl-paste-arm64
│       ├── wl-paste-x86_64
│       ├── xsel-arm64
│       └── xsel-x86_64
├── scripts/
│   ├── afterPack.cjs
│   ├── apply-flavor.mjs
│   ├── build-flatpak.sh
│   ├── build-snap.sh
│   ├── build.worklet.mjs
│   ├── bundle-bridge.mjs
│   ├── bundle-renderer.mjs
│   ├── create-linux-tarball.sh
│   ├── notarize.cjs
│   └── patch-electron-dock-name.mjs
├── snap/
│   └── snapcraft.yaml
├── src/
│   ├── app/
│   │   ├── App/
│   │   │   ├── appConfig.js
│   │   │   ├── hooks/
│   │   │   │   ├── useInactivity.js
│   │   │   │   ├── useInactivity.test.js
│   │   │   │   ├── useOnExtension.test.js
│   │   │   │   ├── useOnExtensionExit.js
│   │   │   │   ├── useOnExtensionLockOut.js
│   │   │   │   ├── useOnExtensionLockOut.test.js
│   │   │   │   ├── useRedirect.js
│   │   │   │   └── useRedirect.test.js
│   │   │   ├── index.js
│   │   │   └── styles.js
│   │   └── Routes/
│   │       └── index.js
│   ├── components/
│   │   ├── AlertBox/
│   │   │   ├── index.tsx
│   │   │   └── styles.ts
│   │   ├── AppHeaderV2/
│   │   │   ├── AppHeaderV2.styles.ts
│   │   │   ├── AppHeaderV2.test.js
│   │   │   ├── AppHeaderV2.tsx
│   │   │   └── index.ts
│   │   ├── BackgroundWithGradient.tsx
│   │   ├── BadgeTextItem/
│   │   │   ├── index.js
│   │   │   ├── index.test.js
│   │   │   └── styles.js
│   │   ├── BannerBox/
│   │   │   ├── index.js
│   │   │   └── styles.js
│   │   ├── ButtonPlusCreateNew/
│   │   │   ├── index.js
│   │   │   ├── index.test.js
│   │   │   └── styles.js
│   │   ├── CardSingleSetting/
│   │   │   ├── index.js
│   │   │   ├── index.test.js
│   │   │   └── styles.js
│   │   ├── CopyButton/
│   │   │   └── index.tsx
│   │   ├── CreateCustomField/
│   │   │   ├── index.js
│   │   │   ├── index.test.js
│   │   │   └── styles.js
│   │   ├── CreateNewCategoryPopupContent/
│   │   │   ├── index.js
│   │   │   ├── index.test.js
│   │   │   └── styles.js
│   │   ├── DropdownSwapVault/
│   │   │   ├── index.test.js
│   │   │   ├── index.tsx
│   │   │   └── styles.ts
│   │   ├── EditFolderPopupContent/
│   │   │   ├── index.js
│   │   │   └── styles.js
│   │   ├── EmptyCollectionView/
│   │   │   ├── index.js
│   │   │   ├── index.test.js
│   │   │   └── styles.js
│   │   ├── FileDropArea/
│   │   │   ├── index.js
│   │   │   ├── index.test.js
│   │   │   └── styles.js
│   │   ├── FileUploadContent/
│   │   │   ├── index.js
│   │   │   └── styles.js
│   │   ├── FolderDropdown/
│   │   │   ├── FolderDropdownV2.tsx
│   │   │   ├── index.js
│   │   │   ├── index.test.js
│   │   │   └── styles.js
│   │   ├── FormGroup/
│   │   │   ├── index.js
│   │   │   ├── index.test.js
│   │   │   └── styles.js
│   │   ├── FormModalHeaderWrapper/
│   │   │   ├── index.js
│   │   │   ├── index.test.js
│   │   │   └── styles.js
│   │   ├── FormWrapper/
│   │   │   ├── index.js
│   │   │   └── index.test.js
│   │   ├── ImportDataOption/
│   │   │   ├── index.js
│   │   │   └── styles.js
│   │   ├── InitialPageWrapper/
│   │   │   ├── index.js
│   │   │   ├── index.test.js
│   │   │   └── styles.js
│   │   ├── InputFieldNote/
│   │   │   ├── index.js
│   │   │   └── index.test.js
│   │   ├── InputPearpassPassword/
│   │   │   ├── index.js
│   │   │   └── styles.js
│   │   ├── InputSearch/
│   │   │   ├── index.js
│   │   │   ├── index.test.js
│   │   │   └── styles.js
│   │   ├── ListItem/
│   │   │   ├── index.js
│   │   │   ├── index.test.js
│   │   │   └── styles.js
│   │   ├── LoadingOverlay/
│   │   │   ├── index.js
│   │   │   └── index.test.js
│   │   ├── MenuDropdown/
│   │   │   ├── MenuDropdownItem/
│   │   │   │   ├── index.js
│   │   │   │   └── index.test.js
│   │   │   ├── MenuDropdownLabel/
│   │   │   │   ├── index.js
│   │   │   │   └── index.test.js
│   │   │   ├── index.js
│   │   │   ├── index.test.js
│   │   │   └── styles.js
│   │   ├── NoticeContainer/
│   │   │   ├── index.js
│   │   │   └── styles.js
│   │   ├── OnboardingShell/
│   │   │   ├── index.tsx
│   │   │   └── styles.ts
│   │   ├── OtpCodeField/
│   │   │   ├── index.test.js
│   │   │   ├── index.ts
│   │   │   └── utils.ts
│   │   ├── OtpCodeFieldV2/
│   │   │   ├── index.test.tsx
│   │   │   ├── index.tsx
│   │   │   └── styles.ts
│   │   ├── Overlay/
│   │   │   ├── index.js
│   │   │   ├── index.test.js
│   │   │   └── styles.js
│   │   ├── PasswordFieldStrengthIndicator/
│   │   │   ├── index.test.tsx
│   │   │   └── index.tsx
│   │   ├── PopupMenu/
│   │   │   ├── index.js
│   │   │   ├── index.test.js
│   │   │   ├── styles.js
│   │   │   └── utils/
│   │   │       ├── getHorizontal.js
│   │   │       ├── getHorizontal.test.js
│   │   │       ├── getVertical.js
│   │   │       └── getVertical.test.js
│   │   ├── RadioSelect/
│   │   │   ├── index.js
│   │   │   ├── index.test.js
│   │   │   └── styles.js
│   │   ├── Record/
│   │   │   ├── index.js
│   │   │   ├── index.test.js
│   │   │   └── styles.js
│   │   ├── RecordActionsPopupContent/
│   │   │   ├── index.js
│   │   │   ├── index.test.js
│   │   │   └── styles.js
│   │   ├── RecordAvatar/
│   │   │   ├── index.test.js
│   │   │   ├── index.tsx
│   │   │   └── styles.ts
│   │   ├── RecordItemIcon/
│   │   │   ├── RecordItemIcon.styles.ts
│   │   │   ├── RecordItemIcon.tsx
│   │   │   └── index.ts
│   │   ├── RecordSortActionsPopupContent/
│   │   │   ├── index.js
│   │   │   ├── index.test.js
│   │   │   └── styles.js
│   │   ├── RecordTypeMenu/
│   │   │   ├── index.js
│   │   │   └── index.test.js
│   │   ├── Select/
│   │   │   ├── SelectItem/
│   │   │   │   ├── index.js
│   │   │   │   ├── index.test.js
│   │   │   │   └── styles.js
│   │   │   ├── SelectLabel/
│   │   │   │   ├── index.js
│   │   │   │   ├── index.test.js
│   │   │   │   └── styles.js
│   │   │   ├── index.js
│   │   │   ├── index.test.js
│   │   │   └── styles.js
│   │   ├── SidebarCategory/
│   │   │   ├── index.js
│   │   │   ├── index.test.js
│   │   │   └── styles.js
│   │   ├── SidebarFolder/
│   │   │   ├── index.js
│   │   │   ├── index.test.js
│   │   │   └── styles.js
│   │   ├── SidebarSearch/
│   │   │   ├── index.js
│   │   │   ├── index.test.js
│   │   │   └── styles.js
│   │   ├── SwitchWithLabel/
│   │   │   ├── index.js
│   │   │   ├── index.test.js
│   │   │   └── styles.js
│   │   ├── TimerBar/
│   │   │   ├── index.test.js
│   │   │   ├── index.ts
│   │   │   └── styles.ts
│   │   ├── TimerCircle/
│   │   │   ├── index.test.js
│   │   │   ├── index.ts
│   │   │   └── styles.ts
│   │   ├── TitleBar/
│   │   │   └── index.js
│   │   ├── Toasts/
│   │   │   ├── index.js
│   │   │   ├── index.test.js
│   │   │   └── styles.js
│   │   └── WebsiteButton/
│   │       └── index.tsx
│   ├── constants/
│   │   ├── appConstants.js
│   │   ├── feedback.js
│   │   ├── formFields.js
│   │   ├── layout.ts
│   │   ├── localStorage.js
│   │   ├── meta.js
│   │   ├── navigation.js
│   │   ├── pairing.js
│   │   ├── password.ts
│   │   ├── pearpassLinks.js
│   │   ├── recordActions.js
│   │   ├── recordColorByType.js
│   │   ├── recordIconByType.js
│   │   ├── securityErrors.js
│   │   ├── services.js
│   │   ├── sortOptions.ts
│   │   ├── timeConstants.js
│   │   └── transitions.js
│   ├── containers/
│   │   ├── AppHeaderContainer/
│   │   │   ├── AppHeaderContainer.test.js
│   │   │   ├── AppHeaderContainer.tsx
│   │   │   └── index.ts
│   │   ├── AttachmentField/
│   │   │   ├── index.js
│   │   │   └── styles.js
│   │   ├── AuthenticationCard/
│   │   │   ├── index.js
│   │   │   └── styles.js
│   │   ├── BaseInitialPage/
│   │   │   ├── index.js
│   │   │   └── styles.js
│   │   ├── CustomFields/
│   │   │   └── index.tsx
│   │   ├── EmptyCollectionViewV2/
│   │   │   ├── EmptyCollectionViewV2.styles.ts
│   │   │   ├── EmptyCollectionViewV2.tsx
│   │   │   └── index.ts
│   │   ├── EmptyResultsViewV2/
│   │   │   ├── EmptyResultsViewV2.styles.ts
│   │   │   ├── EmptyResultsViewV2.tsx
│   │   │   └── index.ts
│   │   ├── ImagesField/
│   │   │   ├── index.js
│   │   │   └── styles.js
│   │   ├── LayoutWithSidebar/
│   │   │   ├── index.js
│   │   │   └── styles.js
│   │   ├── MainViewHeader/
│   │   │   ├── MainViewHeader.styles.ts
│   │   │   ├── MainViewHeader.test.tsx
│   │   │   └── MainViewHeader.tsx
│   │   ├── Modal/
│   │   │   ├── AddDeviceModalContent/
│   │   │   │   ├── ScanQRExpireTimer.tsx
│   │   │   │   ├── index.ts
│   │   │   │   └── styles.ts
│   │   │   ├── AddDeviceModalContentV2/
│   │   │   │   ├── AddDeviceModalContentV2.styles.ts
│   │   │   │   └── AddDeviceModalContentV2.tsx
│   │   │   ├── AuthenticationModalContentV2/
│   │   │   │   ├── index.tsx
│   │   │   │   └── styles.ts
│   │   │   ├── BlindPeersModalContent/
│   │   │   │   ├── index.js
│   │   │   │   └── styles.js
│   │   │   ├── BrowserExtensionDialogV2/
│   │   │   │   ├── index.tsx
│   │   │   │   └── styles.ts
│   │   │   ├── ConfirmationModalContent/
│   │   │   │   ├── index.js
│   │   │   │   └── styles.js
│   │   │   ├── CreateFileEncryptionPassword/
│   │   │   │   ├── index.tsx
│   │   │   │   └── styles.ts
│   │   │   ├── CreateFolderModalContent/
│   │   │   │   ├── index.js
│   │   │   │   └── styles.js
│   │   │   ├── CreateFolderModalContentV2/
│   │   │   │   ├── CreateFolderModalContentV2.styles.ts
│   │   │   │   └── CreateFolderModalContentV2.tsx
│   │   │   ├── CreateOrEditCategoryWrapper/
│   │   │   │   ├── CreateOrEditAuthenticatorModalContent/
│   │   │   │   │   ├── CreateOrEditAuthenticatorModalContent.styles.ts
│   │   │   │   │   └── CreateOrEditAuthenticatorModalContent.tsx
│   │   │   │   ├── CreateOrEditCreditCardModalContent/
│   │   │   │   │   └── index.js
│   │   │   │   ├── CreateOrEditCreditCardModalContentV2/
│   │   │   │   │   ├── CreateOrEditCreditCardModalContentV2.styles.ts
│   │   │   │   │   └── CreateOrEditCreditCardModalContentV2.tsx
│   │   │   │   ├── CreateOrEditCustomModalContent/
│   │   │   │   │   └── index.js
│   │   │   │   ├── CreateOrEditCustomModalContentV2/
│   │   │   │   │   ├── CreateOrEditCustomModalContentV2.styles.ts
│   │   │   │   │   └── CreateOrEditCustomModalContentV2.tsx
│   │   │   │   ├── CreateOrEditIdentityModalContent/
│   │   │   │   │   └── index.js
│   │   │   │   ├── CreateOrEditIdentityModalContentV2/
│   │   │   │   │   ├── CreateOrEditIdentityModalContentV2.styles.ts
│   │   │   │   │   └── CreateOrEditIdentityModalContentV2.tsx
│   │   │   │   ├── CreateOrEditLoginModalContent/
│   │   │   │   │   └── index.js
│   │   │   │   ├── CreateOrEditLoginModalContentV2/
│   │   │   │   │   ├── CreateOrEditLoginModalContentV2.styles.ts
│   │   │   │   │   └── CreateOrEditLoginModalContentV2.tsx
│   │   │   │   ├── CreateOrEditNoteModalContent/
│   │   │   │   │   └── index.js
│   │   │   │   ├── CreateOrEditNoteModalContentV2/
│   │   │   │   │   ├── CreateOrEditNoteModalContentV2.styles.ts
│   │   │   │   │   └── CreateOrEditNoteModalContentV2.tsx
│   │   │   │   ├── CreateOrEditPassPhraseModalContent/
│   │   │   │   │   └── index.js
│   │   │   │   ├── CreateOrEditPassPhraseModalContentV2/
│   │   │   │   │   ├── CreateOrEditPassPhraseModalContentV2.styles.ts
│   │   │   │   │   └── CreateOrEditPassPhraseModalContentV2.tsx
│   │   │   │   ├── CreateOrEditWifiModalContent/
│   │   │   │   │   └── index.js
│   │   │   │   ├── CreateOrEditWifiModalContentV2/
│   │   │   │   │   ├── CreateOrEditWifiModalContentV2.styles.ts
│   │   │   │   │   └── CreateOrEditWifiModalContentV2.tsx
│   │   │   │   └── index.js
│   │   │   ├── CreateOrEditVaultModalContentV2/
│   │   │   │   ├── CreateOrEditVaultModalContentV2.styles.ts
│   │   │   │   └── CreateOrEditVaultModalContentV2.tsx
│   │   │   ├── CreateVaultModalContent/
│   │   │   │   ├── index.js
│   │   │   │   └── styles.js
│   │   │   ├── DecryptFilePassword/
│   │   │   │   └── index.tsx
│   │   │   ├── DeleteFolderModalContentV2/
│   │   │   │   ├── DeleteFolderModalContentV2.styles.ts
│   │   │   │   └── DeleteFolderModalContentV2.tsx
│   │   │   ├── DeleteRecordsModalContentV2/
│   │   │   │   ├── DeleteRecordsModalContentV2.styles.ts
│   │   │   │   ├── DeleteRecordsModalContentV2.tsx
│   │   │   │   └── index.ts
│   │   │   ├── DeleteVaultModalContent/
│   │   │   │   ├── DeviceList.tsx
│   │   │   │   ├── __tests__/
│   │   │   │   │   ├── DeleteVaultModalContent.test.tsx
│   │   │   │   │   └── DeviceList.test.tsx
│   │   │   │   ├── index.tsx
│   │   │   │   ├── styles.ts
│   │   │   │   └── types.ts
│   │   │   ├── DisplayPictureModalContent/
│   │   │   │   ├── index.js
│   │   │   │   └── styles.js
│   │   │   ├── DisplayPictureModalContentV2/
│   │   │   │   ├── DisplayPictureModalContentV2.styles.ts
│   │   │   │   └── DisplayPictureModalContentV2.tsx
│   │   │   ├── ExtensionPairingModalContent/
│   │   │   │   ├── ExtensionPairingModalContentV2.styles.ts
│   │   │   │   ├── ExtensionPairingModalContentV2.tsx
│   │   │   │   ├── index.js
│   │   │   │   └── styles.js
│   │   │   ├── GeneratePasswordModalContentV2/
│   │   │   │   ├── GeneratePasswordModalContentV2.styles.ts
│   │   │   │   └── GeneratePasswordModalContentV2.tsx
│   │   │   ├── GeneratePasswordSideDrawerContent/
│   │   │   │   ├── PassphraseChecker/
│   │   │   │   │   └── index.js
│   │   │   │   ├── PassphraseGenerator/
│   │   │   │   │   └── index..js
│   │   │   │   ├── PasswordChecker/
│   │   │   │   │   └── index.js
│   │   │   │   ├── PasswordGenerator/
│   │   │   │   │   └── index.js
│   │   │   │   ├── RuleSelector/
│   │   │   │   │   └── index.js
│   │   │   │   ├── index.js
│   │   │   │   └── styles.js
│   │   │   ├── ImportItemOrVaultModalContentV2/
│   │   │   │   ├── ImportItemOrVaultModalContentV2.styles.ts
│   │   │   │   └── index.tsx
│   │   │   ├── ImportVaultPreviewModalContent/
│   │   │   │   ├── ImportVaultPreviewModalContent.styles.ts
│   │   │   │   └── index.tsx
│   │   │   ├── ModalContent/
│   │   │   │   ├── index.js
│   │   │   │   └── styles.js
│   │   │   ├── ModalHeader/
│   │   │   │   ├── index.js
│   │   │   │   └── styles.js
│   │   │   ├── ModifyMasterVaultModalContent/
│   │   │   │   ├── index.js
│   │   │   │   └── styles.js
│   │   │   ├── ModifyVaultModalContent/
│   │   │   │   ├── index.js
│   │   │   │   └── styles.js
│   │   │   ├── MoveFolderModalContent/
│   │   │   │   ├── index.js
│   │   │   │   └── styles.js
│   │   │   ├── MoveFolderModalContentV2/
│   │   │   │   ├── MoveFolderModalContentV2.styles.ts
│   │   │   │   └── MoveFolderModalContentV2.tsx
│   │   │   ├── PairedDevicesModalContent/
│   │   │   │   ├── index.tsx
│   │   │   │   └── styles.ts
│   │   │   ├── SideDrawer/
│   │   │   │   ├── index.js
│   │   │   │   └── styles.js
│   │   │   ├── UnsavedChangesModalContent/
│   │   │   │   ├── UnsavedChangesModalContent.tsx
│   │   │   │   └── index.ts
│   │   │   ├── UpdateRequiredModalContent/
│   │   │   │   ├── index.js
│   │   │   │   └── styles.js
│   │   │   ├── UpdateRequiredModalContentV2/
│   │   │   │   ├── UpdateRequiredModalContentV2.styles.ts
│   │   │   │   └── UpdateRequiredModalContentV2.tsx
│   │   │   ├── UploadFilesModalContentV2/
│   │   │   │   ├── UploadFilesModalContentV2.styles.ts
│   │   │   │   ├── UploadFilesModalContentV2.tsx
│   │   │   │   └── index.ts
│   │   │   ├── UploadImageModalContent/
│   │   │   │   ├── index.js
│   │   │   │   └── styles.js
│   │   │   ├── VaultPasswordFormModalContent/
│   │   │   │   ├── index.js
│   │   │   │   └── styles.js
│   │   │   ├── index.js
│   │   │   └── styles.js
│   │   ├── MultiSelectActionsBar/
│   │   │   ├── MultiSelectActionsBar.styles.ts
│   │   │   ├── MultiSelectActionsBar.test.tsx
│   │   │   ├── MultiSelectActionsBar.tsx
│   │   │   └── index.ts
│   │   ├── PassPhrase/
│   │   │   ├── PassPhraseSettings.js
│   │   │   ├── PassPhraseV2.styles.ts
│   │   │   ├── PassPhraseV2.tsx
│   │   │   ├── __tests__/
│   │   │   │   ├── PassPhrase.test.js
│   │   │   │   └── PassPhraseSettings.test.js
│   │   │   ├── index.js
│   │   │   └── styles.js
│   │   ├── RecordDetails/
│   │   │   ├── CreditCardDetailsForm/
│   │   │   │   ├── CreditCardDetailsFormV2.styles.ts
│   │   │   │   ├── CreditCardDetailsFormV2.tsx
│   │   │   │   ├── index.js
│   │   │   │   └── utils.ts
│   │   │   ├── CustomDetailsForm/
│   │   │   │   ├── CustomDetailsFormV2.styles.ts
│   │   │   │   ├── CustomDetailsFormV2.tsx
│   │   │   │   └── index.js
│   │   │   ├── IdentityDetailsForm/
│   │   │   │   ├── IdentityDetailsFormV2.styles.ts
│   │   │   │   ├── IdentityDetailsFormV2.tsx
│   │   │   │   ├── index.js
│   │   │   │   └── utils.ts
│   │   │   ├── LoginRecordDetailsForm/
│   │   │   │   ├── LoginRecordDetailsFormV2.styles.ts
│   │   │   │   ├── LoginRecordDetailsFormV2.tsx
│   │   │   │   ├── index.js
│   │   │   │   └── utils.ts
│   │   │   ├── NoteDetailsForm/
│   │   │   │   ├── NoteDetailsFormV2.styles.ts
│   │   │   │   ├── NoteDetailsFormV2.tsx
│   │   │   │   ├── index.js
│   │   │   │   └── utils.ts
│   │   │   ├── PassPhraseDetailsForm/
│   │   │   │   ├── PassPhraseDetailsFormV2.styles.ts
│   │   │   │   ├── PassPhraseDetailsFormV2.tsx
│   │   │   │   ├── index.js
│   │   │   │   └── utils.ts
│   │   │   ├── RecordDetailsContent/
│   │   │   │   └── index.js
│   │   │   ├── RecordDetailsV2.styles.ts
│   │   │   ├── RecordDetailsV2.tsx
│   │   │   ├── WifiDetailsForm/
│   │   │   │   ├── WifiDetailsFormV2.styles.ts
│   │   │   │   ├── WifiDetailsFormV2.tsx
│   │   │   │   ├── index.js
│   │   │   │   └── utils.ts
│   │   │   ├── index.js
│   │   │   └── styles.js
│   │   ├── RecordListView/
│   │   │   ├── RecordListViewV2.styles.ts
│   │   │   ├── RecordListViewV2.test.tsx
│   │   │   ├── RecordListViewV2.tsx
│   │   │   ├── RecordRowContextMenuV2.styles.ts
│   │   │   ├── RecordRowContextMenuV2.tsx
│   │   │   ├── index.tsx
│   │   │   ├── styles.js
│   │   │   └── utils.js
│   │   ├── Sidebar/
│   │   │   ├── SidebarCategories/
│   │   │   │   ├── index.js
│   │   │   │   └── styles.js
│   │   │   ├── SidebarV2.styles.ts
│   │   │   ├── SidebarV2.test.tsx
│   │   │   ├── SidebarV2.tsx
│   │   │   ├── VaultSelector/
│   │   │   │   ├── VaultSelector.styles.ts
│   │   │   │   └── VaultSelector.tsx
│   │   │   ├── index.js
│   │   │   └── styles.js
│   │   └── WifiPasswordQRCode/
│   │       ├── WifiPasswordQRCodeV2.tsx
│   │       ├── index.js
│   │       └── styles.js
│   ├── context/
│   │   ├── AppHeaderContext.test.js
│   │   ├── AppHeaderContext.tsx
│   │   ├── BannerContext.js
│   │   ├── LoadingContext.js
│   │   ├── LoadingContext.test.js
│   │   ├── ModalContext.js
│   │   ├── ModalContext.test.js
│   │   ├── RouterContext.d.ts
│   │   ├── RouterContext.js
│   │   ├── RouterContext.test.js
│   │   ├── ToastContext.js
│   │   ├── ToastContext.test.js
│   │   └── UnsavedChangesContext.tsx
│   ├── electron/
│   │   ├── index.js
│   │   ├── vaultClientProxy.js
│   │   └── vaultClientProxy.test.js
│   ├── hooks/
│   │   ├── __tests__/
│   │   │   └── useTranslation.test.js
│   │   ├── useAnimatedVisibility.js
│   │   ├── useAnimatedVisibility.test.js
│   │   ├── useAutoLockPreferences.test.js
│   │   ├── useAutoLockPreferences.ts
│   │   ├── useConnectExtension.js
│   │   ├── useConnectExtension.test.js
│   │   ├── useCopyToClipboard.electron.js
│   │   ├── useCopyToClipboard.electron.test.js
│   │   ├── useCreateOrEditRecord.d.ts
│   │   ├── useCreateOrEditRecord.js
│   │   ├── useCreateOrEditRecord.test.js
│   │   ├── useCustomFields.js
│   │   ├── useCustomFields.test.js
│   │   ├── useGetMultipleFiles.js
│   │   ├── useGetMultipleFiles.test.js
│   │   ├── useLanguageOptions.js
│   │   ├── useLanguageOptions.test.js
│   │   ├── useOutsideClick.js
│   │   ├── useOutsideClick.test.js
│   │   ├── usePasteFromClipboard.js
│   │   ├── usePasteFromClipboard.test.js
│   │   ├── usePearUpdate.js
│   │   ├── usePearUpdate.test.js
│   │   ├── useRecordActionItems.d.ts
│   │   ├── useRecordActionItems.js
│   │   ├── useRecordActionItems.test.js
│   │   ├── useRecordMenuItems.js
│   │   ├── useRecordMenuItems.test.js
│   │   ├── useRecordMenuItemsV2.test.ts
│   │   ├── useRecordMenuItemsV2.ts
│   │   ├── useRiveWithRetry.ts
│   │   ├── useScrollOverflow.test.js
│   │   ├── useScrollOverflow.ts
│   │   ├── useSimulatedLoading.js
│   │   ├── useSimulatedLoading.test.js
│   │   ├── useTranslation.ts
│   │   ├── useVaultSwitch.test.tsx
│   │   ├── useVaultSwitch.tsx
│   │   ├── useWindowResize.js
│   │   └── useWindowResize.test.js
│   ├── lib-react-components/
│   │   ├── components/
│   │   │   ├── ButtonCreate/
│   │   │   │   ├── index.js
│   │   │   │   ├── index.test.js
│   │   │   │   └── styles.js
│   │   │   ├── ButtonFilter/
│   │   │   │   ├── index.js
│   │   │   │   ├── index.test.js
│   │   │   │   └── styles.js
│   │   │   ├── ButtonFolder/
│   │   │   │   ├── index.js
│   │   │   │   ├── index.test.js
│   │   │   │   └── styles.js
│   │   │   ├── ButtonLittle/
│   │   │   │   ├── index.js
│   │   │   │   ├── index.test.js
│   │   │   │   └── styles.js
│   │   │   ├── ButtonPrimary/
│   │   │   │   ├── index.js
│   │   │   │   ├── index.test.js
│   │   │   │   └── styles.js
│   │   │   ├── ButtonRadio/
│   │   │   │   ├── index.js
│   │   │   │   ├── index.test.js
│   │   │   │   └── styles.js
│   │   │   ├── ButtonRoundIcon/
│   │   │   │   ├── index.js
│   │   │   │   ├── index.test.js
│   │   │   │   └── styles.js
│   │   │   ├── ButtonSecondary/
│   │   │   │   ├── index.js
│   │   │   │   ├── index.test.js
│   │   │   │   └── styles.js
│   │   │   ├── ButtonSingleInput/
│   │   │   │   ├── index.js
│   │   │   │   ├── index.test.js
│   │   │   │   └── styles.js
│   │   │   ├── ButtonThin/
│   │   │   │   ├── index.js
│   │   │   │   ├── index.test.js
│   │   │   │   └── styles.js
│   │   │   ├── CompoundField/
│   │   │   │   ├── index.js
│   │   │   │   ├── index.test.js
│   │   │   │   └── styles.js
│   │   │   ├── HighlightString/
│   │   │   │   ├── index.js
│   │   │   │   ├── index.test.js
│   │   │   │   └── styles.js
│   │   │   ├── InputField/
│   │   │   │   ├── index.test.js
│   │   │   │   ├── index.tsx
│   │   │   │   └── styles.ts
│   │   │   ├── NoticeText/
│   │   │   │   ├── index.js
│   │   │   │   ├── index.test.js
│   │   │   │   └── styles.js
│   │   │   ├── PasswordField/
│   │   │   │   ├── index.js
│   │   │   │   ├── index.test.js
│   │   │   │   └── styles.js
│   │   │   ├── PearPassInputField/
│   │   │   │   ├── index.js
│   │   │   │   ├── index.test.js
│   │   │   │   └── styles.js
│   │   │   ├── PearPassPasswordField/
│   │   │   │   ├── index.js
│   │   │   │   ├── index.test.js
│   │   │   │   └── styles.js
│   │   │   ├── PearPassPasswordFieldV2/
│   │   │   │   ├── index.test.ts
│   │   │   │   ├── index.tsx
│   │   │   │   ├── styles.ts
│   │   │   │   └── types.ts
│   │   │   ├── Slider/
│   │   │   │   ├── index.js
│   │   │   │   ├── index.test.js
│   │   │   │   └── styles.js
│   │   │   ├── Switch/
│   │   │   │   ├── index.js
│   │   │   │   ├── index.test.js
│   │   │   │   └── styles.js
│   │   │   ├── TextArea/
│   │   │   │   ├── index.test.js
│   │   │   │   ├── index.tsx
│   │   │   │   └── styles.ts
│   │   │   └── TooltipWrapper/
│   │   │       ├── index.js
│   │   │       └── styles.js
│   │   ├── icons/
│   │   │   ├── AboutIcon/
│   │   │   │   └── index.js
│   │   │   ├── AppearanceIcon/
│   │   │   │   └── index.js
│   │   │   ├── ArrowDownIcon/
│   │   │   │   └── index.js
│   │   │   ├── ArrowLeftIcon/
│   │   │   │   └── index.js
│   │   │   ├── ArrowRightIcon/
│   │   │   │   └── index.js
│   │   │   ├── ArrowUpAndDown/
│   │   │   │   └── index.js
│   │   │   ├── ArrowUpIcon/
│   │   │   │   └── index.js
│   │   │   ├── AutoFillIcon/
│   │   │   │   └── index.js
│   │   │   ├── BackIcon/
│   │   │   │   └── index.js
│   │   │   ├── BrushIcon/
│   │   │   │   └── index.js
│   │   │   ├── CalendarIcon/
│   │   │   │   └── index.js
│   │   │   ├── CheckIcon/
│   │   │   │   └── index.js
│   │   │   ├── CollapseIcon/
│   │   │   │   └── index.js
│   │   │   ├── CommonFileIcon/
│   │   │   │   └── index.js
│   │   │   ├── ComputerIcon/
│   │   │   │   └── index.js
│   │   │   ├── CopyIcon/
│   │   │   │   └── index.js
│   │   │   ├── CreditCardIcon/
│   │   │   │   └── index.js
│   │   │   ├── CubeIcon/
│   │   │   │   └── index.js
│   │   │   ├── DeleteIcon/
│   │   │   │   └── index.js
│   │   │   ├── EmailIcon/
│   │   │   │   └── index.js
│   │   │   ├── ErrorIcon/
│   │   │   │   └── index.js
│   │   │   ├── ExitIcon/
│   │   │   │   └── index.js
│   │   │   ├── EyeClosedIcon/
│   │   │   │   └── index.js
│   │   │   ├── EyeIcon/
│   │   │   │   └── index.js
│   │   │   ├── FolderIcon/
│   │   │   │   └── index.js
│   │   │   ├── FullBodyIcon/
│   │   │   │   └── index.js
│   │   │   ├── GenderIcon/
│   │   │   │   └── index.js
│   │   │   ├── GroupIcon/
│   │   │   │   └── index.js
│   │   │   ├── ImageIcon/
│   │   │   │   └── index.js
│   │   │   ├── InfoIcon/
│   │   │   │   └── index.js
│   │   │   ├── KebabMenuIcon/
│   │   │   │   └── index.js
│   │   │   ├── KeyIcon/
│   │   │   │   └── index.js
│   │   │   ├── LockCircleIcon/
│   │   │   │   └── index.js
│   │   │   ├── LockIcon/
│   │   │   │   └── index.js
│   │   │   ├── MoveToIcon/
│   │   │   │   └── index.js
│   │   │   ├── MultiSelectionIcon/
│   │   │   │   └── index.js
│   │   │   ├── NationalityIcon/
│   │   │   │   └── index.js
│   │   │   ├── NewFolderIcon/
│   │   │   │   └── index.js
│   │   │   ├── NineDotsIcon/
│   │   │   │   └── index.js
│   │   │   ├── NoteIcon/
│   │   │   │   └── index.js
│   │   │   ├── OkayIcon/
│   │   │   │   └── index.js
│   │   │   ├── OutsideLinkIcon/
│   │   │   │   └── index.js
│   │   │   ├── PassPhraseIcon/
│   │   │   │   └── index.js
│   │   │   ├── PasswordIcon/
│   │   │   │   └── index.js
│   │   │   ├── PasteIcon/
│   │   │   │   └── index.js
│   │   │   ├── PhoneIcon/
│   │   │   │   └── index.js
│   │   │   ├── PinIcon/
│   │   │   │   └── index.js
│   │   │   ├── PlusIcon/
│   │   │   │   └── index.js
│   │   │   ├── SaveIcon/
│   │   │   │   └── index.js
│   │   │   ├── SearchIcon/
│   │   │   │   └── index.js
│   │   │   ├── SecurityIcon/
│   │   │   │   └── index.js
│   │   │   ├── SettingsIcon/
│   │   │   │   └── index.js
│   │   │   ├── ShareIcon/
│   │   │   │   └── index.js
│   │   │   ├── SmallArrowIcon/
│   │   │   │   └── index.js
│   │   │   ├── StarIcon/
│   │   │   │   └── index.js
│   │   │   ├── SyncingIcon/
│   │   │   │   └── index.js
│   │   │   ├── TimeIcon/
│   │   │   │   └── index.js
│   │   │   ├── UserIcon/
│   │   │   │   └── index.js
│   │   │   ├── UserSecurityIcon/
│   │   │   │   └── index.js
│   │   │   ├── VaultIcon/
│   │   │   │   └── index.js
│   │   │   ├── WifiIcon/
│   │   │   │   └── index.js
│   │   │   ├── WorldIcon/
│   │   │   │   └── index.js
│   │   │   ├── XIcon/
│   │   │   │   └── index.js
│   │   │   ├── YellowErrorIcon/
│   │   │   │   └── index.js
│   │   │   └── icons.test.js
│   │   ├── illustrations/
│   │   │   └── AuthenticatorIllustration/
│   │   │       └── index.js
│   │   ├── index.js
│   │   └── utils/
│   │       ├── getIconProps.js
│   │       └── getIconProps.test.js
│   ├── pages/
│   │   ├── AuthenticatorView/
│   │   │   ├── index.js
│   │   │   ├── index.test.js
│   │   │   └── styles.ts
│   │   ├── InitialPage/
│   │   │   └── index.js
│   │   ├── Intro/
│   │   │   ├── CategoryAnimation/
│   │   │   │   └── index.tsx
│   │   │   ├── CreditCardAnimation/
│   │   │   │   └── index.tsx
│   │   │   ├── GradientContainer/
│   │   │   │   ├── index.js
│   │   │   │   └── styles.js
│   │   │   ├── IntroV2.tsx
│   │   │   ├── IntroV2Styles.ts
│   │   │   ├── OnboardingLockVideo/
│   │   │   │   └── index.tsx
│   │   │   ├── PasswordFillAnimation/
│   │   │   │   ├── index.tsx
│   │   │   │   └── styles.js
│   │   │   ├── SyncWithoutCloudAnimation/
│   │   │   │   └── index.tsx
│   │   │   ├── TutorialContainer/
│   │   │   │   ├── index.js
│   │   │   │   └── styles.js
│   │   │   ├── WelcomeToPearpass/
│   │   │   │   ├── index.js
│   │   │   │   └── styles.js
│   │   │   ├── index.js
│   │   │   └── styles.js
│   │   ├── LoadingPage/
│   │   │   ├── LoadingPageV2.tsx
│   │   │   ├── LoadingPageV2Styles.ts
│   │   │   ├── index.js
│   │   │   └── styles.js
│   │   ├── MainView/
│   │   │   ├── MainViewV2.styles.ts
│   │   │   ├── MainViewV2.tsx
│   │   │   ├── index.js
│   │   │   └── styles.js
│   │   ├── SettingsView/
│   │   │   ├── AboutContent/
│   │   │   │   ├── index.js
│   │   │   │   └── index.test.js
│   │   │   ├── AppearanceContent/
│   │   │   │   └── index.js
│   │   │   ├── ExportTab/
│   │   │   │   ├── index.js
│   │   │   │   ├── styles.js
│   │   │   │   └── utils/
│   │   │   │       ├── downloadFile.js
│   │   │   │       ├── downloadFile.test.js
│   │   │   │       ├── downloadZip.js
│   │   │   │       ├── downloadZip.test.js
│   │   │   │       ├── exportCsvPerVault.js
│   │   │   │       └── exportJsonPerVault.js
│   │   │   ├── ImportTab/
│   │   │   │   ├── index.js
│   │   │   │   ├── styles.js
│   │   │   │   └── utils/
│   │   │   │       ├── readFileContent.js
│   │   │   │       └── readFileContent.test.js
│   │   │   ├── SecurityContent/
│   │   │   │   └── index.js
│   │   │   ├── SettingsAdvancedTab/
│   │   │   │   ├── SettingsAutoLockConfiguration/
│   │   │   │   │   ├── index.tsx
│   │   │   │   │   └── styles.ts
│   │   │   │   ├── SettingsBlindPeersSection/
│   │   │   │   │   ├── index.js
│   │   │   │   │   ├── index.test.js
│   │   │   │   │   └── styles.js
│   │   │   │   ├── index.js
│   │   │   │   └── styles.js
│   │   │   ├── SettingsTab/
│   │   │   │   ├── SettingsDevicesSection/
│   │   │   │   │   ├── index.js
│   │   │   │   │   ├── index.test.js
│   │   │   │   │   └── styles.js
│   │   │   │   ├── SettingsLanguageSection/
│   │   │   │   │   ├── index.js
│   │   │   │   │   ├── index.test.js
│   │   │   │   │   └── styles.js
│   │   │   │   ├── SettingsPasswordsSection/
│   │   │   │   │   ├── index.js
│   │   │   │   │   └── styles.js
│   │   │   │   ├── SettingsReportSection/
│   │   │   │   │   ├── index.js
│   │   │   │   │   ├── index.test.js
│   │   │   │   │   └── styles.js
│   │   │   │   ├── index.js
│   │   │   │   └── styles.js
│   │   │   ├── SettingsVaultsTab/
│   │   │   │   ├── index.js
│   │   │   │   ├── index.test.js
│   │   │   │   └── styles.js
│   │   │   ├── SyncingContent/
│   │   │   │   └── index.js
│   │   │   ├── VaultContent/
│   │   │   │   └── index.js
│   │   │   ├── index.js
│   │   │   └── styles.js
│   │   ├── SettingsViewV2/
│   │   │   ├── SettingsViewV2.styles.ts
│   │   │   ├── SettingsViewV2.tsx
│   │   │   └── content/
│   │   │       ├── AppPreferencesContent/
│   │   │       │   ├── index.test.tsx
│   │   │       │   ├── index.tsx
│   │   │       │   └── styles.ts
│   │   │       ├── AppVersionContent/
│   │   │       │   ├── index.tsx
│   │   │       │   └── styles.ts
│   │   │       ├── BlindPeersContent/
│   │   │       │   ├── index.test.tsx
│   │   │       │   ├── index.tsx
│   │   │       │   └── styles.ts
│   │   │       ├── DiagnosticsContent/
│   │   │       │   ├── index.test.tsx
│   │   │       │   ├── index.tsx
│   │   │       │   └── styles.ts
│   │   │       ├── ExportItemsContent/
│   │   │       │   ├── index.test.tsx
│   │   │       │   ├── index.tsx
│   │   │       │   └── styles.ts
│   │   │       ├── ImportCodesContent/
│   │   │       │   ├── index.tsx
│   │   │       │   └── styles.ts
│   │   │       ├── ImportItemsContent/
│   │   │       │   ├── index.test.tsx
│   │   │       │   ├── index.tsx
│   │   │       │   └── styles.ts
│   │   │       ├── LanguageContent/
│   │   │       │   ├── index.tsx
│   │   │       │   └── styles.ts
│   │   │       ├── MasterPasswordContent/
│   │   │       │   ├── index.test.tsx
│   │   │       │   ├── index.tsx
│   │   │       │   └── styles.ts
│   │   │       ├── ReportAProblemContent/
│   │   │       │   ├── index.test.tsx
│   │   │       │   ├── index.tsx
│   │   │       │   └── styles.ts
│   │   │       ├── YourDevicesContent/
│   │   │       │   ├── index.test.tsx
│   │   │       │   ├── index.tsx
│   │   │       │   └── styles.ts
│   │   │       ├── YourVaultsContent/
│   │   │       │   ├── index.test.tsx
│   │   │       │   ├── index.tsx
│   │   │       │   └── styles.ts
│   │   │       └── index.ts
│   │   └── WelcomePage/
│   │       ├── CardCreateMasterPassword/
│   │       │   ├── index.js
│   │       │   └── styles.js
│   │       ├── CardCreateMasterPasswordV2/
│   │       │   ├── index.tsx
│   │       │   └── styles.ts
│   │       ├── CardLoadVault/
│   │       │   ├── index.js
│   │       │   └── styles.js
│   │       ├── CardNewVaultCredentials/
│   │       │   ├── index.js
│   │       │   └── styles.js
│   │       ├── CardUnlockPearPass/
│   │       │   └── index.tsx
│   │       ├── CardUnlockPearPassV2/
│   │       │   ├── index.tsx
│   │       │   └── styles.ts
│   │       ├── CardUnlockVault/
│   │       │   ├── index.js
│   │       │   └── styles.js
│   │       ├── CardUploadBackupFile/
│   │       │   ├── index.js
│   │       │   └── styles.js
│   │       ├── CardVaultSelect/
│   │       │   ├── index.js
│   │       │   └── styles.js
│   │       ├── LockedScreen/
│   │       │   ├── Timer.js
│   │       │   ├── index.js
│   │       │   └── styles.js
│   │       ├── LockedScreenV2/
│   │       │   ├── LockedScreenV2.styles.ts
│   │       │   ├── LockedScreenV2.test.tsx
│   │       │   └── LockedScreenV2.tsx
│   │       ├── index.js
│   │       └── styles.js
│   ├── services/
│   │   ├── createOrGetPearpassClient.js
│   │   ├── createOrGetPearpassClient.test.js
│   │   ├── createOrGetPipe.js
│   │   ├── createOrGetPipe.test.js
│   │   ├── handlers/
│   │   │   ├── EncryptionHandlers.js
│   │   │   ├── EncryptionHandlers.test.js
│   │   │   ├── SecureRequestHandler.js
│   │   │   ├── SecureRequestHandler.test.js
│   │   │   ├── SecurityHandlers.js
│   │   │   ├── SecurityHandlers.test.js
│   │   │   └── VaultHandlers.js
│   │   ├── ipc/
│   │   │   ├── MethodRegistry.js
│   │   │   ├── MethodRegistry.test.js
│   │   │   ├── SocketManager.js
│   │   │   └── SocketManager.test.js
│   │   ├── nativeMessagingIPCServer.js
│   │   ├── nativeMessagingIPCServer.test.js
│   │   ├── nativeMessagingPreferences.js
│   │   ├── nativeMessagingPreferences.test.js
│   │   └── security/
│   │       ├── appIdentity.js
│   │       ├── appIdentity.test.js
│   │       ├── protocolConstants.js
│   │       ├── sessionManager.js
│   │       ├── sessionManager.test.js
│   │       └── sessionStore.js
│   ├── shared/
│   │   ├── commandDefinitions.js
│   │   └── types.ts
│   ├── strict.css
│   ├── svgs/
│   │   ├── ItemCardIllustration/
│   │   │   ├── ItemCardIllustration.tsx
│   │   │   └── index.ts
│   │   ├── LogoLock/
│   │   │   ├── index.js
│   │   │   └── index.test.js
│   │   ├── OnboardingLock/
│   │   │   └── index.js
│   │   ├── PearLogo/
│   │   │   └── index.js
│   │   ├── PearpassLogo/
│   │   │   └── index.js
│   │   ├── ProtonPass/
│   │   │   └── index.js
│   │   ├── SpotlightLeft/
│   │   │   ├── index.js
│   │   │   └── index.test.js
│   │   ├── SpotlightMiddle/
│   │   │   ├── index.js
│   │   │   └── index.test.js
│   │   └── SpotlightRight/
│   │       ├── index.js
│   │       └── index.test.js
│   ├── types/
│   │   ├── css.d.ts
│   │   ├── electron.d.ts
│   │   ├── jest-globals.d.ts
│   │   ├── modules.d.ts
│   │   └── styled.d.ts
│   └── utils/
│       ├── addHttps.js
│       ├── addHttps.test.js
│       ├── applyGlobalStyles.js
│       ├── applyGlobalStyles.test.js
│       ├── autoLock.js
│       ├── autoLock.test.js
│       ├── breakpoints.js
│       ├── breakpoints.test.js
│       ├── createErrorWithCode.js
│       ├── createErrorWithCode.test.js
│       ├── designVersion.js
│       ├── devicePreferences.cjs
│       ├── devicePreferences.test.js
│       ├── envGetter.js
│       ├── envGetter.test.js
│       ├── extractDomainName.js
│       ├── extractDomainName.test.js
│       ├── formatPasskeyDate.js
│       ├── getDeviceName.js
│       ├── getDeviceName.test.js
│       ├── getFilteredAttachmentsById.js
│       ├── getFilteredAttachmentsById.test.js
│       ├── getPasswordStrengthInfo.test.ts
│       ├── getPasswordStrengthInfo.ts
│       ├── getRecordSubtitle.test.ts
│       ├── getRecordSubtitle.ts
│       ├── groupRecordsByTimePeriod.test.ts
│       ├── groupRecordsByTimePeriod.ts
│       ├── handleFileSelect.js
│       ├── handleFileSelect.test.js
│       ├── isFavorite.js
│       ├── isFavorite.test.js
│       ├── isOnline.js
│       ├── isPasswordChangeReminderDisabled.js
│       ├── isPasswordChangeReminderDisabled.test.js
│       ├── logHelper.cjs
│       ├── logHelper.test.js
│       ├── logger.js
│       ├── logger.test.js
│       ├── nativeMessagingSetup.js
│       ├── nativeMessagingSetup.test.js
│       ├── sortByName.js
│       ├── sortByName.test.js
│       ├── toSentenceCase.js
│       ├── toSentenceCase.test.js
│       ├── vaultCreated.js
│       ├── vaultCreated.test.js
│       ├── withAlpha.test.js
│       └── withAlpha.ts
├── styles.js
└── tsconfig.json
Download .txt
SYMBOL INDEX (790 symbols across 224 files)

FILE: app.electron.tsx
  function renderApp (line 37) | function renderApp() {
  function init (line 66) | async function init() {

FILE: appling/lib/install.cjs
  constant WINDOW_HEIGHT (line 8) | const WINDOW_HEIGHT = 548;
  constant WINDOW_WIDTH (line 9) | const WINDOW_WIDTH = 500;
  function install (line 11) | async function install(id, opts = {}) {

FILE: appling/lib/preflight.cjs
  function preflight (line 12) | async function preflight(id) {

FILE: appling/lib/progress.cjs
  class Progress (line 11) | class Progress {
    method constructor (line 18) | constructor(app, stages = [1]) {
    method _broadcast (line 38) | _broadcast() {
    method _compute (line 54) | _compute() {
    method update (line 59) | update(u, stage = 0) {
    method stage (line 66) | stage(stage, value) {
    method complete (line 74) | complete() {

FILE: appling/lib/utils.cjs
  function encode (line 8) | function encode(msg) {
  function decode (line 17) | function decode(msg) {
  function format (line 32) | function format(u) {

FILE: appling/lib/view.html.cjs
  constant AUTO_LAUNCH (line 1) | const AUTO_LAUNCH = true
  constant SLOW_TIMEOUT (line 2) | const SLOW_TIMEOUT = 180000 // 3 minutes

FILE: appling/lib/worker.cjs
  function setup (line 14) | function setup(data) {
  function validateConfig (line 22) | function validateConfig() {
  function install (line 38) | async function install() {

FILE: e2e/components/CreateOrEditPage.js
  class CreateOrEditPage (line 3) | class CreateOrEditPage {
    method constructor (line 4) | constructor(root) {
    method getCreateOrEditInputField (line 10) | getCreateOrEditInputField(field) {
    method getCreateOrEditTextareaField (line 23) | getCreateOrEditTextareaField(field) {
    method fillCreateOrEditInput (line 27) | async fillCreateOrEditInput(field, value) {
    method verifyPasswordToNotHaveValue (line 34) | async verifyPasswordToNotHaveValue(password) {
    method getCreateOrEditButton (line 42) | getCreateOrEditButton(name) {
    method clickOnCreateOrEditButton (line 50) | async clickOnCreateOrEditButton(button) {
    method saveButton (line 56) | get saveButton() {
    method elementItemCloseButton (line 60) | get elementItemCloseButton() {
    method clickElementItemCloseButton (line 64) | async clickElementItemCloseButton() {
    method detailsWebsite (line 71) | get detailsWebsite() {
    method verifyDetailsWebsiteCount (line 75) | async verifyDetailsWebsiteCount(expectedCount) {
    method detailsComment (line 79) | get detailsComment() {
    method verifyDetailsCommentCount (line 83) | async verifyDetailsCommentCount(expectedCount) {
    method passwordMenu (line 89) | get passwordMenu() {
    method openPasswordMenu (line 93) | async openPasswordMenu() {
    method insertPasswordButton (line 98) | get insertPasswordButton() {
    method clickInsertPasswordButton (line 104) | async clickInsertPasswordButton() {
    method elementItemPassword (line 111) | get elementItemPassword() {
    method passwordInput (line 115) | get passwordInput() {
    method elementItemPasswordShowHideFirst (line 119) | get elementItemPasswordShowHideFirst() {
    method elementItemPasswordShowHideLast (line 125) | get elementItemPasswordShowHideLast() {
    method clickShowHidePasswordButtonFirst (line 129) | async clickShowHidePasswordButtonFirst() {
    method clickShowHidePasswordButtonLast (line 134) | async clickShowHidePasswordButtonLast() {
    method verifyPasswordType (line 139) | async verifyPasswordType(password_type) {
    method getCreateOrEditUploadAttachment (line 147) | getCreateOrEditUploadAttachment() {
    method clickOnAttachment (line 151) | async clickOnAttachment() {
    method deleteAttachmentButton (line 157) | get deleteAttachmentButton() {
    method clickOnDeleteAttachmentButton (line 161) | async clickOnDeleteAttachmentButton() {
    method loadFile (line 167) | get loadFile() {
    method fileInput (line 171) | get fileInput() {
    method uploadFile (line 175) | async uploadFile() {
    method uploadedFileLink (line 179) | get uploadedFileLink() {
    method uploadedFile (line 185) | get uploadedFile() {
    method uploadedImage (line 189) | get uploadedImage() {
    method clickOnUploadedFile (line 193) | async clickOnUploadedFile() {
    method verifyUploadedFileIsVisible (line 198) | async verifyUploadedFileIsVisible() {
    method verifyUploadedImageIsVisible (line 203) | async verifyUploadedImageIsVisible() {
    method createCustomNote (line 209) | get createCustomNote() {
    method customNoteInput (line 213) | get customNoteInput() {
    method customNoteInput_first (line 217) | get customNoteInput_first() {
    method fillCustomNoteInput (line 221) | async fillCustomNoteInput() {
    method fillCustomNoteInput_first (line 228) | async fillCustomNoteInput_first() {
    method deleteCustomNote (line 235) | async deleteCustomNote() {
    method dropdownFolderMenu (line 243) | get dropdownFolderMenu() {
    method openDropdownMenu (line 247) | async openDropdownMenu() {
    method selectFromDropdownMenu (line 252) | async selectFromDropdownMenu(foldername) {
    method getSection (line 260) | getSection(sectionname) {
    method identitySection (line 264) | get identitySection() {
    method clickOnIdentitySection (line 268) | async clickOnIdentitySection(sectionname) {
    method passPhrasePasteButton (line 276) | get passPhrasePasteButton() {
    method clickOnPasteFromClipboard (line 280) | async clickOnPasteFromClipboard() {
    method verifyItemDetailsValue (line 288) | async verifyItemDetailsValue(labelOrPlaceholder, expectedValue) {
    method verifyItemDetailsValueIsNotVisible (line 293) | async verifyItemDetailsValueIsNotVisible(labelOrPlaceholder) {

FILE: e2e/components/DetailsPage.js
  class DetailsPage (line 3) | class DetailsPage {
    method constructor (line 4) | constructor(root) {
    method itemDetailsCounter (line 10) | get itemDetailsCounter() {
    method verifyDetailsNoItems (line 16) | async verifyDetailsNoItems() {
    method getItemDetailsTitle (line 22) | get getItemDetailsTitle() {
    method verifyTitle (line 26) | async verifyTitle(expectedTitle) {
    method getElementItemDetails (line 32) | getElementItemDetails(labelOrPlaceholder) {
    method verifyItemDetailsValue (line 61) | async verifyItemDetailsValue(labelOrPlaceholder, expectedValue) {
    method verifyItemDetailsValueIsNotVisible (line 66) | async verifyItemDetailsValueIsNotVisible(labelOrPlaceholder) {
    method getItemDetailsCommentInput (line 73) | get getItemDetailsCommentInput() {
    method verifyCustomNoteText (line 77) | async verifyCustomNoteText(expectedText) {
    method getElementItemDetailsNew (line 82) | getElementItemDetailsNew() {
    method verifyNoteText (line 86) | async verifyNoteText(note_text) {
    method getIdentityDetails (line 94) | getIdentityDetails(name) {
    method verifyIdentityDetails (line 126) | async verifyIdentityDetails(name) {
    method verifyIdentityDetailsValue (line 131) | async verifyIdentityDetailsValue(name, expectedValue) {
    method recoveryPhraseDetails (line 138) | get recoveryPhraseDetails() {
    method verifyAllRecoveryPhraseWords (line 142) | async verifyAllRecoveryPhraseWords(expectedWords) {
    method elementItemFileLink (line 153) | get elementItemFileLink() {
    method uploadedImage (line 159) | get uploadedImage() {
    method clickOnUploadedFile (line 163) | async clickOnUploadedFile() {
    method verifyUploadedFileIsVisible (line 168) | async verifyUploadedFileIsVisible() {
    method verifyUploadedImageIsVisible (line 172) | async verifyUploadedImageIsVisible() {
    method detailsBarActionsButton (line 178) | get detailsBarActionsButton() {
    method detailsBarEditButton (line 182) | get detailsBarEditButton() {
    method detailsBarFavoriteButton (line 186) | get detailsBarFavoriteButton() {
    method detailsBarThreeDots (line 190) | get detailsBarThreeDots() {
    method openItemBarThreeDotsDropdownMenu (line 194) | async openItemBarThreeDotsDropdownMenu() {
    method editElement (line 199) | async editElement() {
    method markAsFavoriteButton (line 206) | get markAsFavoriteButton() {
    method removeFromFavoritesButton (line 210) | get removeFromFavoritesButton() {
    method clickMarkAsFavoriteButton (line 214) | async clickMarkAsFavoriteButton() {
    method clickRemoveFromFavoritesButton (line 219) | async clickRemoveFromFavoritesButton() {
    method clickFavoriteButton (line 224) | async clickFavoriteButton() {
    method elementItemCloseButton (line 233) | get elementItemCloseButton() {
    method clickElementItemCloseButton (line 237) | async clickElementItemCloseButton() {
    method getCreateNewFolderTitleInput (line 244) | getCreateNewFolderTitleInput() {
    method createFolderButton (line 250) | get createFolderButton() {
    method fillCreateNewFolderTitleInput (line 254) | async fillCreateNewFolderTitleInput(value) {
    method clickCreateFolderButton (line 258) | async clickCreateFolderButton() {
    method getItemDetailsFolderName (line 264) | getItemDetailsFolderName(foldername) {
    method verifyItemDetailsFolderName (line 268) | async verifyItemDetailsFolderName(foldername) {
    method recordListContainer (line 275) | get recordListContainer() {
    method clickShowHidePasswordButton (line 281) | async clickShowHidePasswordButton() {
    method clickPasswordToggle (line 286) | async clickPasswordToggle(slotTestId) {
    method verifyPasswordFieldType (line 292) | async verifyPasswordFieldType(slotTestId, expectedType) {

FILE: e2e/components/LoginPage.js
  class LoginPage (line 3) | class LoginPage {
    method constructor (line 4) | constructor(root) {
    method title (line 10) | get title() {
    method waitForReady (line 14) | async waitForReady(timeout = 30000) {
    method passwordInput (line 20) | get passwordInput() {
    method enterPassword (line 24) | async enterPassword(password) {
    method continueButton (line 31) | get continueButton() {
    method clickContinue (line 35) | async clickContinue() {
    method loginToApplication (line 39) | async loginToApplication(password) {

FILE: e2e/components/MainPage.js
  class MainPage (line 3) | class MainPage {
    method constructor (line 4) | constructor(root) {
    method element (line 10) | get element() {
    method getElementByPosition (line 14) | getElementByPosition(position) {
    method clickOnFirstElement (line 22) | async clickOnFirstElement() {
    method openElementDetails (line 27) | async openElementDetails() {
    method verifyElementTitle (line 32) | async verifyElementTitle(title) {
    method verifyElementIsNotVisible (line 37) | async verifyElementIsNotVisible() {
    method verifyElementByPosition (line 41) | async verifyElementByPosition(position, element_name) {
    method clickElementByPosition (line 45) | async clickElementByPosition(position, element_name) {
    method elementCheckBox (line 51) | async elementCheckBox(expectedState) {
    method mainViewFavoriteIcon (line 61) | get mainViewFavoriteIcon() {
    method clickOnMainViewFavoriteIcon (line 65) | async clickOnMainViewFavoriteIcon() {
    method favoriteIconDisabled (line 70) | async favoriteIconDisabled() {
    method favoriteIconEnabled (line 75) | async favoriteIconEnabled() {
    method mainViewHeaderSelect (line 82) | get mainViewHeaderSelect() {
    method multipleSelectionButon (line 86) | get multipleSelectionButon() {
    method multipleSelectDeleteButon (line 90) | get multipleSelectDeleteButon() {
    method multipleSelectMoveButon (line 94) | get multipleSelectMoveButon() {
    method multipleSelectCancelButon (line 98) | get multipleSelectCancelButon() {
    method multipleSelectCheckerByPosition (line 102) | get multipleSelectCheckerByPosition() {
    method clickMainViewHeaderSelect (line 109) | async clickMainViewHeaderSelect() {
    method clickMultipleSelectiontButton (line 114) | async clickMultipleSelectiontButton() {
    method clickMultipleSelectDeletetButton (line 119) | async clickMultipleSelectDeletetButton() {
    method clickMultipleSelectMoveButon (line 124) | async clickMultipleSelectMoveButon() {
    method verifyMultipleSelectDeleteButtonIsEnabled (line 129) | async verifyMultipleSelectDeleteButtonIsEnabled() {
    method mainPlusButon (line 136) | get mainPlusButon() {
    method clickAddItem (line 140) | async clickAddItem(type) {
    method sortButon (line 151) | get sortButon() {
    method getSortOption (line 155) | getSortOption(option) {
    method clickSortButton (line 159) | async clickSortButton() {
    method selectSortOption (line 164) | async selectSortOption(option) {
    method createNewFolderinputFolderName (line 171) | get createNewFolderinputFolderName() {
    method createFolderModalButton (line 175) | get createFolderModalButton() {
    method getCollectionButton (line 179) | getCollectionButton(button_name) {
    method verifyElementFolderName (line 183) | async verifyElementFolderName(elementfoldername) {
    method clickMoveFolderChip (line 192) | async clickMoveFolderChip(folderName) {
    method clickMoveFolderSubmit (line 198) | async clickMoveFolderSubmit() {
    method emptyCollectionView (line 207) | get emptyCollectionView() {
    method verifyEmptyCollection (line 211) | async verifyEmptyCollection() {
    method detailsCloseButton (line 217) | get detailsCloseButton() {
    method clickDetailsCloseButton (line 221) | async clickDetailsCloseButton() {
    method clickYesButton (line 232) | async clickYesButton() {

FILE: e2e/components/SettingsPage.js
  class SettingsPage (line 3) | class SettingsPage {
    method constructor (line 4) | constructor(root) {
    method getSettingsDropdownSection (line 10) | getSettingsDropdownSection(section_name) {
    method verifySettingsDropdownSectionIsVisible (line 14) | async verifySettingsDropdownSectionIsVisible(section_name) {
    method getSettingsDropdownNavigation (line 19) | getSettingsDropdownNavigation(section_navigation_name) {
    method verifySettingsDropdownNavigationIsVisible (line 23) | async verifySettingsDropdownNavigationIsVisible(section_navigation_nam...
    method backSettingsButton (line 28) | get backSettingsButton() {
    method clickBackSettingsButton (line 32) | async clickBackSettingsButton() {
    method getPearPassFunctionDropdown (line 39) | getPearPassFunctionDropdown(pearpass_dropdown_id) {
    method clickPearPassFunctionDropdown (line 43) | async clickPearPassFunctionDropdown(dropdown_id) {
    method getPearPassFunctionDropdownOption (line 49) | getPearPassFunctionDropdownOption(dropdown_option) {
    method verifyPearPassFunctionDropdownOptionIsVisible (line 53) | async verifyPearPassFunctionDropdownOptionIsVisible(dropdown_id) {

FILE: e2e/components/SideMenuPage.js
  class SideMenuPage (line 3) | class SideMenuPage {
    method constructor (line 4) | constructor(root) {
    method sidebarExitButton (line 10) | get sidebarExitButton() {
    method clickSidebarExitButton (line 14) | async clickSidebarExitButton() {
    method sidebarSettingsButton (line 21) | get sidebarSettingsButton() {
    method clickSidebarSettingsButton (line 25) | async clickSidebarSettingsButton() {
    method getSidebarCategory (line 32) | getSidebarCategory(categoryname) {
    method selectSideBarCategory (line 36) | async selectSideBarCategory(name) {
    method favoritesFolder (line 45) | get favoritesFolder() {
    method verifySideBarFavoritesFolder (line 49) | async verifySideBarFavoritesFolder(items) {
    method getSideMenuFolder (line 56) | getSideMenuFolder(folderName) {
    method sidebarAddButton (line 61) | get sidebarAddButton() {
    method confirmButton (line 65) | get confirmButton() {
    method deleteFolderButton (line 69) | get deleteFolderButton() {
    method clickSidebarAddButton (line 73) | async clickSidebarAddButton() {
    method createFolder (line 78) | async createFolder(name) {
    method openSideBarFolder (line 89) | async openSideBarFolder(foldername) {
    method deleteMultipleItemsFolder (line 94) | async deleteMultipleItemsFolder(foldername) {
    method deleteFolder (line 103) | async deleteFolder(foldername) {
    method clickDeleteFolderButton (line 113) | async clickDeleteFolderButton() {
    method verifySidebarFolderName (line 118) | async verifySidebarFolderName(foldername) {

FILE: e2e/components/Utilities.js
  class Utilities (line 3) | class Utilities {
    method constructor (line 4) | constructor(root) {
    method deleteAllElements (line 10) | async deleteAllElements() {
    method pasteFromClipboard (line 46) | async pasteFromClipboard(locator, text) {

FILE: e2e/fixtures/app.runner.js
  function resolveElectronBinary (line 11) | function resolveElectronBinary(appDir) {
  function sleep (line 16) | function sleep(ms) {
  function connectWithRetries (line 20) | async function connectWithRetries(wsEndpoint, maxRetries) {
  function waitForPage (line 44) | async function waitForPage(browser, maxRetries = 60) {
  function launchApp (line 81) | async function launchApp(appDir) {
  function teardown (line 181) | async function teardown({ proc, browser, isWindows }) {

FILE: e2e/scripts/explore.js
  function explore (line 4) | async function explore() {

FILE: electron/clipboardCleanup.cjs
  constant DEFAULT_CLIPBOARD_CLEAR_DELAY_MS (line 6) | const DEFAULT_CLIPBOARD_CLEAR_DELAY_MS = 30000
  constant CLIPBOARD_CLEANUP_STATE_FILE (line 7) | const CLIPBOARD_CLEANUP_STATE_FILE = 'pearpass-clipboard-cleanup-current...
  function getClipboardCleanupStatePath (line 9) | function getClipboardCleanupStatePath(app) {
  function removeFileIfExists (line 13) | function removeFileIfExists(filePath) {
  function removeClipboardCleanupTokenIfCurrent (line 21) | function removeClipboardCleanupTokenIfCurrent(statePath, token) {
  function spawnDetachedClipboardHelper (line 32) | function spawnDetachedClipboardHelper(secretPath, token, statePath, dela...
  function spawnDetachedWindowsClipboardHelper (line 52) | function spawnDetachedWindowsClipboardHelper(
  function scheduleClipboardCleanup (line 97) | function scheduleClipboardCleanup({

FILE: electron/clipboardCleanupHelper.cjs
  function removeFileIfExists (line 7) | function removeFileIfExists(filePath) {
  function readSecretFromFile (line 15) | function readSecretFromFile(secretPath) {
  function readCurrentToken (line 23) | function readCurrentToken(statePath) {
  function clearCurrentTokenIfMatches (line 32) | function clearCurrentTokenIfMatches(statePath, token) {
  function describeLinuxSession (line 38) | function describeLinuxSession() {
  function logLinuxClipboardSkip (line 45) | function logLinuxClipboardSkip(sessionLabel) {
  function sleep (line 51) | function sleep(delayMs) {
  function runClipboardCommand (line 55) | function runClipboardCommand(command, args, input) {
  function readClipboard (line 63) | function readClipboard() {
  function clearClipboard (line 87) | function clearClipboard() {
  function runClipboardCleanup (line 110) | async function runClipboardCleanup({
  function main (line 141) | async function main(argv = process.argv) {

FILE: electron/flatpak-paths.cjs
  function isFlatpakRuntime (line 4) | function isFlatpakRuntime(options = {}) {
  function isSnapRuntime (line 12) | function isSnapRuntime(options = {}) {
  function getSnapRealHome (line 17) | function getSnapRealHome(options = {}) {
  function getHostHome (line 25) | function getHostHome(options = {}) {
  function getFlatpakCompatRoots (line 35) | function getFlatpakCompatRoots(options = {}) {
  function mapFlatpakPathToSandbox (line 48) | function mapFlatpakPathToSandbox(targetPath, options = {}) {
  function getSandboxSafePath (line 71) | function getSandboxSafePath(targetPath, options = {}) {

FILE: electron/linuxWaylandClipboard.cjs
  function isWaylandSession (line 8) | function isWaylandSession() {
  function runCommand (line 15) | function runCommand(command, args, input) {
  function readClipboard (line 23) | function readClipboard() {
  function clearClipboard (line 39) | function clearClipboard() {

FILE: electron/linuxWaylandClipboardFallback.cjs
  constant TEMP_WL_PASTE_NAME (line 6) | const TEMP_WL_PASTE_NAME = 'pearpass-wl-paste'
  constant TEMP_WL_COPY_NAME (line 7) | const TEMP_WL_COPY_NAME = 'pearpass-wl-copy'
  function getWlPasteBinaryArchitectureName (line 9) | function getWlPasteBinaryArchitectureName() {
  function getWlCopyBinaryArchitectureName (line 13) | function getWlCopyBinaryArchitectureName() {
  function resolveBundledBinarySourcePath (line 17) | function resolveBundledBinarySourcePath(archName) {
  function prepareBundledBinary (line 27) | function prepareBundledBinary(archName, tempName) {
  function prepareWlPasteBinary (line 51) | function prepareWlPasteBinary() {
  function prepareWlCopyBinary (line 58) | function prepareWlCopyBinary() {
  function readClipboardWithFallback (line 65) | function readClipboardWithFallback() {
  function clearClipboardWithFallback (line 90) | function clearClipboardWithFallback() {

FILE: electron/linuxX11Clipboard.cjs
  function runCommand (line 8) | function runCommand(command, args, input) {
  function readClipboard (line 16) | function readClipboard() {
  function clearClipboard (line 32) | function clearClipboard() {

FILE: electron/linuxX11ClipboardFallback.cjs
  constant TEMP_BINARY_NAME (line 6) | const TEMP_BINARY_NAME = 'pearpass-xsel'
  function getBinaryArchitectureName (line 12) | function getBinaryArchitectureName() {
  function resolveBundledBinarySourcePath (line 32) | function resolveBundledBinarySourcePath() {
  function prepareBundledBinary (line 53) | function prepareBundledBinary() {
  function readClipboardWithFallback (line 83) | function readClipboardWithFallback() {
  function clearClipboardWithFallback (line 108) | function clearClipboardWithFallback() {

FILE: electron/main.cjs
  function emitStartupMarker (line 72) | function emitStartupMarker(name, detail) {
  function getExecPath (line 111) | function getExecPath() {
  function getWorkletPath (line 118) | function getWorkletPath() {
  function getStorageDir (line 136) | function getStorageDir() {
  function resolveRuntimeStorageDir (line 145) | async function resolveRuntimeStorageDir() {
  function getNativeBridgePath (line 188) | function getNativeBridgePath() {
  function clearVaultStorageForDevReset (line 202) | function clearVaultStorageForDevReset(storageDir) {
  constant WORKLET_READY_TIMEOUT_MS (line 224) | const WORKLET_READY_TIMEOUT_MS = 15000
  constant WORKLET_READY_SIGNAL (line 225) | const WORKLET_READY_SIGNAL = 'WORKLET_READY'
  function waitForWorkletReady (line 227) | function waitForWorkletReady(sidecar) {
  function startRuntime (line 266) | async function startRuntime() {
  function startWorkletOnly (line 379) | async function startWorkletOnly() {
  function createWindow (line 470) | function createWindow() {
  function fromSerializableArg (line 547) | function fromSerializableArg(data) {
  function toSerializableArg (line 564) | function toSerializableArg(value) {
  function registerIPC (line 581) | function registerIPC() {
  function cleanup (line 756) | async function cleanup() {

FILE: electron/runtime-config.cjs
  function readDesignVersion (line 9) | function readDesignVersion() {

FILE: forge.config.cjs
  function getWindowsKitVersion (line 10) | function getWindowsKitVersion() {

FILE: scripts/afterPack.cjs
  constant ARCH_NAMES (line 22) | const ARCH_NAMES = {
  function prunePrebuilds (line 38) | async function prunePrebuilds(context) {
  function pruneTree (line 75) | async function pruneTree(base, target, stats) {
  function pruneOneLevel (line 100) | async function pruneOneLevel(dir, target, stats) {
  function safeReadDir (line 115) | async function safeReadDir(dir) {
  function dirSize (line 123) | async function dirSize(dir) {
  function wrapLinuxNoSandbox (line 148) | async function wrapLinuxNoSandbox(context) {

FILE: scripts/apply-flavor.mjs
  constant STABLE_NAME (line 26) | const STABLE_NAME = 'PearPass'
  constant NIGHTLY_NAME (line 27) | const NIGHTLY_NAME = 'PearPass-nightly'
  constant STABLE_APP_ID (line 28) | const STABLE_APP_ID = 'com.pears.pass'
  constant NIGHTLY_APP_ID (line 29) | const NIGHTLY_APP_ID = 'com.pears.pass.nightly'
  constant MSIX_STABLE_IDENTITY (line 30) | const MSIX_STABLE_IDENTITY = 'PearPass'
  constant MSIX_NIGHTLY_IDENTITY (line 31) | const MSIX_NIGHTLY_IDENTITY = 'PearPass-Nightly'
  function rewriteJson (line 52) | function rewriteJson(relPath, mutate) {

FILE: scripts/build.worklet.mjs
  method setup (line 25) | setup(build) {
  function buildWorklet (line 34) | async function buildWorklet() {

FILE: scripts/bundle-renderer.mjs
  function shouldTransformStrictDomRuntime (line 31) | function shouldTransformStrictDomRuntime(filePath) {
  function getLoader (line 35) | function getLoader(filePath) {
  function strictDomBabelPlugin (line 42) | function strictDomBabelPlugin() {
  function strictDomCssPlugin (line 66) | function strictDomCssPlugin() {

FILE: src/app/App/hooks/useInactivity.js
  constant DEDUPE_WINDOW_MS (line 18) | const DEDUPE_WINDOW_MS = 50
  function useInactivity (line 23) | function useInactivity() {

FILE: src/app/App/hooks/useOnExtension.test.js
  function TestComponent (line 38) | function TestComponent() {
  function TestComponent (line 66) | function TestComponent() {

FILE: src/app/App/hooks/useOnExtensionLockOut.test.js
  function TestComponent (line 31) | function TestComponent() {
  function TestComponent (line 59) | function TestComponent() {

FILE: src/components/AlertBox/index.tsx
  type AlertBoxType (line 6) | enum AlertBoxType {
  type Props (line 11) | interface Props {

FILE: src/components/AlertBox/styles.ts
  type ContainerProps (line 4) | interface ContainerProps {

FILE: src/components/AppHeaderV2/AppHeaderV2.tsx
  type AppHeaderV2Props (line 9) | type AppHeaderV2Props = {
  type AppHeaderAddItemTriggerProps (line 65) | type AppHeaderAddItemTriggerProps = {

FILE: src/components/BackgroundWithGradient.tsx
  type ContainerProps (line 5) | interface ContainerProps {
  type BackgroundWithGradientProps (line 28) | interface BackgroundWithGradientProps {

FILE: src/components/CopyButton/index.tsx
  type CopyButtonProps (line 7) | interface CopyButtonProps {

FILE: src/components/CreateCustomField/index.js
  constant OPTIONS (line 16) | const OPTIONS = [

FILE: src/components/DropdownSwapVault/index.tsx
  type DropdownSwapVaultProps (line 31) | interface DropdownSwapVaultProps {

FILE: src/components/DropdownSwapVault/styles.ts
  constant ROW_VERTICAL_PADDING (line 5) | const ROW_VERTICAL_PADDING = 9
  constant ROW_HORIZONTAL_PADDING (line 6) | const ROW_HORIZONTAL_PADDING = 10
  constant ROW_MIN_HEIGHT (line 7) | const ROW_MIN_HEIGHT = 42

FILE: src/components/FolderDropdown/FolderDropdownV2.tsx
  type FolderDropdownV2Props (line 20) | type FolderDropdownV2Props = {

FILE: src/components/FolderDropdown/index.js
  constant NO_FOLDER (line 17) | const NO_FOLDER = 'no-folder'

FILE: src/components/OnboardingShell/index.tsx
  type OnboardingShellProps (line 13) | interface OnboardingShellProps {

FILE: src/components/OnboardingShell/styles.ts
  type SolidBackgroundProps (line 4) | interface SolidBackgroundProps {

FILE: src/components/OtpCodeField/index.ts
  type OtpCodeFieldProps (line 10) | interface OtpCodeFieldProps {

FILE: src/components/OtpCodeFieldV2/index.tsx
  constant TIMER_ANIMATION_DURATION (line 18) | const TIMER_ANIMATION_DURATION = 1000
  type OtpCodeFieldV2Props (line 20) | interface OtpCodeFieldV2Props {

FILE: src/components/Overlay/styles.js
  constant SCRIM_BLUR (line 5) | const SCRIM_BLUR = 'rgba(0, 0, 0, 0.5)'

FILE: src/components/PasswordFieldStrengthIndicator/index.tsx
  type IPasswordField (line 11) | interface IPasswordField {
  type IPasswordFieldStrengthIndicatorProps (line 19) | interface IPasswordFieldStrengthIndicatorProps {

FILE: src/components/PopupMenu/styles.js
  constant TRANSITION_DURATION (line 3) | const TRANSITION_DURATION = 250
  constant TRANSFORM_BY_DIRECTION (line 5) | const TRANSFORM_BY_DIRECTION = {

FILE: src/components/RecordAvatar/index.tsx
  type Props (line 15) | interface Props {

FILE: src/components/RecordAvatar/styles.ts
  type AvatarSize (line 3) | type AvatarSize = 'md' | 'sm'
  constant AVATAR_CONTAINER_SIZE (line 5) | const AVATAR_CONTAINER_SIZE = '30px'
  type AvatarContainerProps (line 7) | interface AvatarContainerProps {
  type AvatarAltProps (line 11) | interface AvatarAltProps {

FILE: src/components/RecordItemIcon/RecordItemIcon.tsx
  type RecordLike (line 10) | type RecordLike = {
  type RecordItemIconProps (line 18) | type RecordItemIconProps = {

FILE: src/components/TimerBar/index.ts
  type TimerBarProps (line 7) | interface TimerBarProps {

FILE: src/components/TimerCircle/index.test.js
  constant CIRCUMFERENCE (line 26) | const CIRCUMFERENCE = 2 * Math.PI * 5.5

FILE: src/components/TimerCircle/index.ts
  constant SIZE (line 7) | const SIZE = 14
  constant RADIUS (line 8) | const RADIUS = 5.5
  constant STROKE_WIDTH (line 9) | const STROKE_WIDTH = 1.5
  constant CENTER (line 10) | const CENTER = SIZE / 2
  constant CIRCUMFERENCE (line 11) | const CIRCUMFERENCE = 2 * Math.PI * RADIUS
  type TimerCircleProps (line 13) | interface TimerCircleProps {

FILE: src/components/WebsiteButton/index.tsx
  type WebsiteButtonProps (line 4) | interface WebsiteButtonProps {

FILE: src/constants/appConstants.js
  constant DEBUG_MODE (line 1) | const DEBUG_MODE = false

FILE: src/constants/feedback.js
  constant SLACK_WEBHOOK_URL_PATH (line 24) | const SLACK_WEBHOOK_URL_PATH = isDev()
  constant GOOGLE_FORM_KEY (line 28) | const GOOGLE_FORM_KEY = isDev()
  constant GOOGLE_FORM_MAPPING (line 32) | const GOOGLE_FORM_MAPPING = isDev()

FILE: src/constants/formFields.js
  constant ATTACHMENTS_FIELD_KEY (line 1) | const ATTACHMENTS_FIELD_KEY = 'attachments'

FILE: src/constants/layout.ts
  constant HEADER_MIN_HEIGHT (line 2) | const HEADER_MIN_HEIGHT = 44
  constant FADE_GRADIENT_HEIGHT (line 5) | const FADE_GRADIENT_HEIGHT = 70

FILE: src/constants/localStorage.js
  constant LOCAL_STORAGE_KEYS (line 1) | const LOCAL_STORAGE_KEYS = {

FILE: src/constants/meta.js
  constant META_URL (line 1) | const META_URL = import.meta.url

FILE: src/constants/navigation.js
  constant NAVIGATION_ROUTES (line 1) | const NAVIGATION_ROUTES = {

FILE: src/constants/pairing.js
  constant PAIRING_STATES (line 4) | const PAIRING_STATES = {

FILE: src/constants/password.ts
  constant STRENGTH_MAP (line 3) | const STRENGTH_MAP: Record<string, PasswordIndicatorVariant> = {

FILE: src/constants/pearpassLinks.js
  constant CHROME_EXTENSION_STORE_LINK (line 1) | const CHROME_EXTENSION_STORE_LINK =

FILE: src/constants/recordActions.js
  constant RECORD_ACTION_ICON_BY_TYPE (line 11) | const RECORD_ACTION_ICON_BY_TYPE = {

FILE: src/constants/recordColorByType.js
  constant RECORD_COLOR_BY_TYPE (line 3) | const RECORD_COLOR_BY_TYPE = {

FILE: src/constants/recordIconByType.js
  constant RECORD_ICON_BY_TYPE (line 13) | const RECORD_ICON_BY_TYPE = {

FILE: src/constants/services.js
  constant HANDLER_EVENTS (line 1) | const HANDLER_EVENTS = {

FILE: src/constants/sortOptions.ts
  constant SORT_KEYS (line 1) | const SORT_KEYS = {
  type SortKey (line 9) | type SortKey = (typeof SORT_KEYS)[keyof typeof SORT_KEYS]
  constant SORT_BY_TYPE (line 11) | const SORT_BY_TYPE: Record<

FILE: src/constants/timeConstants.js
  constant COPY_FEEDBACK_DISPLAY_TIME (line 1) | const COPY_FEEDBACK_DISPLAY_TIME = 2000

FILE: src/constants/transitions.js
  constant BASE_TRANSITION_DURATION (line 1) | const BASE_TRANSITION_DURATION = 300

FILE: src/containers/AppHeaderContainer/AppHeaderContainer.test.js
  method AUTHENTICATOR_ENABLED (line 9) | get AUTHENTICATOR_ENABLED() {

FILE: src/containers/CustomFields/index.tsx
  type CustomField (line 8) | interface CustomField {
  type CustomFieldsProps (line 14) | interface CustomFieldsProps {

FILE: src/containers/EmptyCollectionViewV2/EmptyCollectionViewV2.styles.ts
  constant CONTENT_MAX_WIDTH (line 3) | const CONTENT_MAX_WIDTH = 500
  constant BUTTONS_MAX_WIDTH (line 4) | const BUTTONS_MAX_WIDTH = 350
  constant ILLUSTRATION_HEIGHT (line 5) | const ILLUSTRATION_HEIGHT = 151

FILE: src/containers/EmptyCollectionViewV2/EmptyCollectionViewV2.tsx
  type EmptyCollectionViewV2Props (line 17) | type EmptyCollectionViewV2Props = {

FILE: src/containers/MainViewHeader/MainViewHeader.styles.ts
  constant SORT_MENU_WIDTH (line 6) | const SORT_MENU_WIDTH = 260

FILE: src/containers/MainViewHeader/MainViewHeader.tsx
  type MainViewHeaderProps (line 28) | type MainViewHeaderProps = {

FILE: src/containers/Modal/AddDeviceModalContent/ScanQRExpireTimer.tsx
  type Props (line 5) | interface Props {

FILE: src/containers/Modal/AuthenticationModalContentV2/index.tsx
  type AuthenticationModalContentV2Props (line 24) | type AuthenticationModalContentV2Props = {

FILE: src/containers/Modal/CreateFileEncryptionPassword/index.tsx
  type props (line 17) | interface props {

FILE: src/containers/Modal/CreateFolderModalContentV2/CreateFolderModalContentV2.tsx
  type CreateFolderModalContentV2Props (line 11) | interface CreateFolderModalContentV2Props {

FILE: src/containers/Modal/CreateOrEditCategoryWrapper/CreateOrEditAuthenticatorModalContent/CreateOrEditAuthenticatorModalContent.tsx
  type CreateOrEditAuthenticatorModalContentProps (line 35) | type CreateOrEditAuthenticatorModalContentProps = {

FILE: src/containers/Modal/CreateOrEditCategoryWrapper/CreateOrEditCreditCardModalContentV2/CreateOrEditCreditCardModalContentV2.tsx
  type CreateOrEditCreditCardModalContentV2Props (line 38) | type CreateOrEditCreditCardModalContentV2Props = {

FILE: src/containers/Modal/CreateOrEditCategoryWrapper/CreateOrEditCustomModalContentV2/CreateOrEditCustomModalContentV2.tsx
  type CreateOrEditCustomModalContentV2Props (line 37) | type CreateOrEditCustomModalContentV2Props = {

FILE: src/containers/Modal/CreateOrEditCategoryWrapper/CreateOrEditIdentityModalContentV2/CreateOrEditIdentityModalContentV2.tsx
  type IdentityData (line 38) | type IdentityData = {
  type CreateOrEditIdentityModalContentV2Props (line 69) | type CreateOrEditIdentityModalContentV2Props = {

FILE: src/containers/Modal/CreateOrEditCategoryWrapper/CreateOrEditLoginModalContentV2/CreateOrEditLoginModalContentV2.tsx
  type CreateOrEditLoginModalContentV2Props (line 47) | type CreateOrEditLoginModalContentV2Props = {

FILE: src/containers/Modal/CreateOrEditCategoryWrapper/CreateOrEditNoteModalContentV2/CreateOrEditNoteModalContentV2.tsx
  type CreateOrEditNoteModalContentV2Props (line 37) | type CreateOrEditNoteModalContentV2Props = {

FILE: src/containers/Modal/CreateOrEditCategoryWrapper/CreateOrEditPassPhraseModalContentV2/CreateOrEditPassPhraseModalContentV2.tsx
  type CreateOrEditPassPhraseModalContentV2Props (line 31) | type CreateOrEditPassPhraseModalContentV2Props = {

FILE: src/containers/Modal/CreateOrEditCategoryWrapper/CreateOrEditWifiModalContentV2/CreateOrEditWifiModalContentV2.tsx
  type CreateOrEditWifiModalContentV2Props (line 33) | type CreateOrEditWifiModalContentV2Props = {

FILE: src/containers/Modal/CreateOrEditVaultModalContentV2/CreateOrEditVaultModalContentV2.tsx
  type CreateOrEditVaultModalContentV2Props (line 15) | type CreateOrEditVaultModalContentV2Props = {

FILE: src/containers/Modal/DecryptFilePassword/index.tsx
  type props (line 22) | interface props {

FILE: src/containers/Modal/DeleteFolderModalContentV2/DeleteFolderModalContentV2.tsx
  type DeleteFolderModalContentV2Props (line 18) | interface DeleteFolderModalContentV2Props {
  type DeleteOption (line 24) | enum DeleteOption {

FILE: src/containers/Modal/DeleteRecordsModalContentV2/DeleteRecordsModalContentV2.tsx
  type DeleteRecordsModalContentV2Props (line 16) | type DeleteRecordsModalContentV2Props = {

FILE: src/containers/Modal/DeleteVaultModalContent/__tests__/DeleteVaultModalContent.test.tsx
  method PROTECTED_VAULT_ENABLED (line 14) | get PROTECTED_VAULT_ENABLED() {

FILE: src/containers/Modal/DeleteVaultModalContent/types.ts
  type FlowType (line 1) | enum FlowType {
  type DeleteVaultModalContentProps (line 6) | type DeleteVaultModalContentProps = {
  type Device (line 11) | interface Device {

FILE: src/containers/Modal/DisplayPictureModalContentV2/DisplayPictureModalContentV2.tsx
  type DisplayPictureModalContentV2Props (line 10) | interface DisplayPictureModalContentV2Props {

FILE: src/containers/Modal/ExtensionPairingModalContent/ExtensionPairingModalContentV2.tsx
  type ExtensionPairingModalContentV2Props (line 20) | type ExtensionPairingModalContentV2Props = {

FILE: src/containers/Modal/GeneratePasswordModalContentV2/GeneratePasswordModalContentV2.tsx
  constant PASSWORD_OPTIONS (line 33) | const PASSWORD_OPTIONS = {
  type PasswordOption (line 38) | type PasswordOption = (typeof PASSWORD_OPTIONS)[keyof typeof PASSWORD_OP...
  type PasswordRules (line 40) | type PasswordRules = {
  type PassphraseRules (line 45) | type PassphraseRules = {
  constant STRENGTH_TO_INDICATOR (line 52) | const STRENGTH_TO_INDICATOR: Record<string, PasswordIndicatorVariant> = {
  type GeneratePasswordModalContentV2Props (line 58) | type GeneratePasswordModalContentV2Props = {

FILE: src/containers/Modal/ImportVaultPreviewModalContent/index.tsx
  function loginWebsiteUrl (line 17) | function loginWebsiteUrl(record: VaultRecord): string {
  function getRecordSubtitle (line 31) | function getRecordSubtitle(record: VaultRecord): string | undefined {

FILE: src/containers/Modal/ModifyVaultModalContent/index.js
  constant UPDATE_MODE (line 30) | const UPDATE_MODE = {

FILE: src/containers/Modal/MoveFolderModalContentV2/MoveFolderModalContentV2.tsx
  constant CHIP_ID_ALL (line 18) | const CHIP_ID_ALL = '__all__'
  type MoveFolderRecord (line 20) | type MoveFolderRecord = Record<string, unknown> & {
  type MoveFolderModalContentV2Props (line 33) | type MoveFolderModalContentV2Props = {
  type FolderOption (line 38) | type FolderOption = {
  function getRecordSubtitle (line 44) | function getRecordSubtitle(record: MoveFolderRecord): string {

FILE: src/containers/Modal/UnsavedChangesModalContent/UnsavedChangesModalContent.tsx
  type UnsavedChangesModalContentProps (line 6) | type UnsavedChangesModalContentProps = {

FILE: src/containers/Modal/UpdateRequiredModalContentV2/UpdateRequiredModalContentV2.tsx
  type UpdateRequiredModalContentV2Props (line 10) | type UpdateRequiredModalContentV2Props = {

FILE: src/containers/Modal/UploadFilesModalContentV2/UploadFilesModalContentV2.tsx
  type UploadFilesModalContentV2Props (line 14) | type UploadFilesModalContentV2Props = {

FILE: src/containers/MultiSelectActionsBar/MultiSelectActionsBar.tsx
  type MultiSelectActionsBarProps (line 14) | type MultiSelectActionsBarProps = {
  type HoverButtonProps (line 23) | type HoverButtonProps = {

FILE: src/containers/PassPhrase/PassPhraseV2.tsx
  type PassPhraseV2Props (line 27) | type PassPhraseV2Props = {

FILE: src/containers/RecordDetails/CreditCardDetailsForm/CreditCardDetailsFormV2.tsx
  type Attachment (line 21) | type Attachment = {
  type CustomField (line 28) | type CustomField = {
  type CreditCardRecord (line 34) | type CreditCardRecord = {
  type CreditCardDetailsFormV2Props (line 50) | type CreditCardDetailsFormV2Props = {
  type CreditCardDetailsFormValues (line 55) | type CreditCardDetailsFormValues = {
  constant IMAGE_EXTENSIONS (line 67) | const IMAGE_EXTENSIONS = ['jpg', 'jpeg', 'png', 'gif', 'webp', 'bmp']

FILE: src/containers/RecordDetails/CreditCardDetailsForm/utils.ts
  type FieldRegistration (line 1) | type FieldRegistration = {

FILE: src/containers/RecordDetails/CustomDetailsForm/CustomDetailsFormV2.tsx
  type Attachment (line 22) | type Attachment = {
  type CustomField (line 29) | type CustomField = {
  type CustomRecord (line 35) | type CustomRecord = {
  type CustomDetailsFormV2Props (line 46) | type CustomDetailsFormV2Props = {
  type CustomDetailsFormValues (line 51) | type CustomDetailsFormValues = {
  constant IMAGE_EXTENSIONS (line 58) | const IMAGE_EXTENSIONS = ['jpg', 'jpeg', 'png', 'gif', 'webp', 'bmp']

FILE: src/containers/RecordDetails/IdentityDetailsForm/IdentityDetailsFormV2.tsx
  type Attachment (line 23) | type Attachment = {
  type CustomField (line 30) | type CustomField = {
  type IdentityRecord (line 36) | type IdentityRecord = {
  type FileFieldName (line 74) | type FileFieldName =
  type AttachmentSource (line 80) | type AttachmentSource = {
  constant IMAGE_EXTENSIONS (line 85) | const IMAGE_EXTENSIONS = ['jpg', 'jpeg', 'png', 'gif', 'webp', 'bmp']
  type IdentityDetailsFormV2Props (line 137) | type IdentityDetailsFormV2Props = {
  type IdentityDetailsFormValues (line 142) | type IdentityDetailsFormValues = {

FILE: src/containers/RecordDetails/IdentityDetailsForm/utils.ts
  type FieldRegistration (line 1) | type FieldRegistration = {

FILE: src/containers/RecordDetails/LoginRecordDetailsForm/LoginRecordDetailsFormV2.tsx
  type Attachment (line 31) | type Attachment = {
  type CustomField (line 38) | type CustomField = {
  type LoginRecord (line 44) | type LoginRecord = {
  type LoginRecordDetailsFormV2Props (line 62) | type LoginRecordDetailsFormV2Props = {
  type LoginRecordDetailsFormValues (line 67) | type LoginRecordDetailsFormValues = {
  constant IMAGE_EXTENSIONS (line 79) | const IMAGE_EXTENSIONS = ['jpg', 'jpeg', 'png', 'gif', 'webp', 'bmp']

FILE: src/containers/RecordDetails/LoginRecordDetailsForm/utils.ts
  type FieldRegistration (line 1) | type FieldRegistration = {

FILE: src/containers/RecordDetails/NoteDetailsForm/NoteDetailsFormV2.tsx
  type Attachment (line 23) | type Attachment = {
  type CustomField (line 30) | type CustomField = {
  type NoteRecord (line 36) | type NoteRecord = {
  type NoteDetailsFormV2Props (line 47) | type NoteDetailsFormV2Props = {
  type NoteDetailsFormValues (line 52) | type NoteDetailsFormValues = {
  constant IMAGE_EXTENSIONS (line 59) | const IMAGE_EXTENSIONS = ['jpg', 'jpeg', 'png', 'gif', 'webp', 'bmp']

FILE: src/containers/RecordDetails/NoteDetailsForm/utils.ts
  type FieldRegistration (line 1) | type FieldRegistration = {

FILE: src/containers/RecordDetails/PassPhraseDetailsForm/PassPhraseDetailsFormV2.tsx
  type CustomField (line 16) | type CustomField = {
  type PassPhraseRecord (line 22) | type PassPhraseRecord = {
  type PassPhraseDetailsFormV2Props (line 33) | type PassPhraseDetailsFormV2Props = {
  type PassPhraseDetailsFormValues (line 38) | type PassPhraseDetailsFormValues = {

FILE: src/containers/RecordDetails/PassPhraseDetailsForm/utils.ts
  type FieldRegistration (line 1) | type FieldRegistration = {

FILE: src/containers/RecordDetails/RecordDetailsV2.tsx
  type RecordAction (line 31) | type RecordAction = {
  constant ACTION_ICON_BY_TYPE (line 37) | const ACTION_ICON_BY_TYPE: Record<
  type RecordShape (line 62) | type RecordShape = {

FILE: src/containers/RecordDetails/WifiDetailsForm/WifiDetailsFormV2.tsx
  type CustomField (line 18) | type CustomField = {
  type WifiRecord (line 24) | type WifiRecord = {
  type WifiDetailsFormV2Props (line 35) | type WifiDetailsFormV2Props = {
  type WifiDetailsFormValues (line 40) | type WifiDetailsFormValues = {

FILE: src/containers/RecordDetails/WifiDetailsForm/utils.ts
  type FieldRegistration (line 1) | type FieldRegistration = {

FILE: src/containers/RecordListView/RecordListViewV2.tsx
  constant ROW_RECORD_ID_ATTR (line 21) | const ROW_RECORD_ID_ATTR = 'data-record-id'
  type RecordListViewV2Props (line 23) | type RecordListViewV2Props = {
  type ActiveContextMenu (line 33) | type ActiveContextMenu = {
  constant SECTION_TITLE_KEYS (line 38) | const SECTION_TITLE_KEYS: Record<string, string> = {

FILE: src/containers/RecordListView/RecordRowContextMenuV2.styles.ts
  constant RECORD_ROW_CONTEXT_MENU_WIDTH (line 4) | const RECORD_ROW_CONTEXT_MENU_WIDTH = 240

FILE: src/containers/RecordListView/RecordRowContextMenuV2.tsx
  constant VIEWPORT_MARGIN (line 37) | const VIEWPORT_MARGIN = 8
  constant MENU_BOX_SHADOW (line 39) | const MENU_BOX_SHADOW =
  type RecordRowContextMenuV2Props (line 42) | type RecordRowContextMenuV2Props = {
  type FileAttachment (line 53) | type FileAttachment = { id: string; name: string }

FILE: src/containers/RecordListView/index.tsx
  constant ITEM_HEIGHT_RECORD (line 39) | const ITEM_HEIGHT_RECORD = 45
  constant ITEM_HEIGHT_HEADER (line 40) | const ITEM_HEIGHT_HEADER = 30
  constant ITEM_GAP (line 41) | const ITEM_GAP = 5
  constant OVERSCAN (line 42) | const OVERSCAN = 5
  type VirtualItem (line 44) | type VirtualItem =
  type SortType (line 48) | type SortType = 'recent' | 'newToOld' | 'oldToNew'
  type RecordItem (line 50) | type RecordItem = {
  type SortAction (line 67) | type SortAction = {
  type RecordListViewProps (line 73) | type RecordListViewProps = {

FILE: src/containers/Sidebar/SidebarV2.styles.ts
  constant SIDEBAR_WIDTH_EXPANDED (line 6) | const SIDEBAR_WIDTH_EXPANDED = 250
  constant SIDEBAR_WIDTH_COLLAPSED (line 7) | const SIDEBAR_WIDTH_COLLAPSED = 57
  constant SIDEBAR_HORIZONTAL_PADDING (line 9) | const SIDEBAR_HORIZONTAL_PADDING = rawTokens.spacing12
  constant FOLDERS_HEADER_HORIZONTAL_PADDING (line 10) | const FOLDERS_HEADER_HORIZONTAL_PADDING = rawTokens.spacing4
  constant COLLAPSED_SMALL_ICON_BUTTON_WIDTH (line 13) | const COLLAPSED_SMALL_ICON_BUTTON_WIDTH = 26
  constant COLLAPSED_CHEVRON_WIDTH (line 14) | const COLLAPSED_CHEVRON_WIDTH = 16
  constant COLLAPSE_BUTTON_CENTER_SHIFT_PX (line 17) | const COLLAPSE_BUTTON_CENTER_SHIFT_PX =
  constant FOLDERS_CHEVRON_CENTER_SHIFT_PX (line 23) | const FOLDERS_CHEVRON_CENTER_SHIFT_PX =
  constant FOLDER_CONTEXT_MENU_WIDTH (line 27) | const FOLDER_CONTEXT_MENU_WIDTH = 220

FILE: src/containers/Sidebar/SidebarV2.tsx
  type FolderRowProps (line 444) | type FolderRowProps = {

FILE: src/containers/Sidebar/VaultSelector/VaultSelector.styles.ts
  constant VAULT_ACTIONS_MENU_WIDTH (line 4) | const VAULT_ACTIONS_MENU_WIDTH = 215

FILE: src/containers/Sidebar/VaultSelector/VaultSelector.tsx
  type VaultSelectorProps (line 42) | type VaultSelectorProps = {
  type VaultRowProps (line 174) | type VaultRowProps = {

FILE: src/containers/WifiPasswordQRCode/WifiPasswordQRCodeV2.tsx
  type Props (line 9) | interface Props {

FILE: src/context/AppHeaderContext.tsx
  type AppHeaderContextState (line 11) | type AppHeaderContextState = {

FILE: src/context/ModalContext.js
  constant STACK_CLEANUP_BUFFER (line 20) | const STACK_CLEANUP_BUFFER = 100
  constant DEFAULT_MODAL_PARAMS (line 26) | const DEFAULT_MODAL_PARAMS = {

FILE: src/context/ModalContext.test.js
  constant CLOSE_DURATION (line 9) | const CLOSE_DURATION = BASE_TRANSITION_DURATION + STACK_CLEANUP_BUFFER

FILE: src/context/RouterContext.d.ts
  type RouterData (line 4) | type RouterData = {
  type RouterContextValue (line 12) | type RouterContextValue = {

FILE: src/context/UnsavedChangesContext.tsx
  type UnsavedChangesGuard (line 10) | type UnsavedChangesGuard = {
  type UnsavedChangesContextValue (line 16) | type UnsavedChangesContextValue = {
  type UnsavedChangesProviderProps (line 25) | type UnsavedChangesProviderProps = {

FILE: src/electron/index.js
  function getElectronConfig (line 14) | function getElectronConfig() {
  function getElectronVaultClient (line 24) | function getElectronVaultClient() {
  function isElectron (line 51) | function isElectron() {

FILE: src/electron/vaultClientProxy.js
  function isBufferLike (line 10) | function isBufferLike(value) {
  function toSerializableArg (line 17) | function toSerializableArg(value) {
  function fromSerializableData (line 40) | function fromSerializableData(data) {
  constant VAULT_METHODS (line 60) | const VAULT_METHODS = [
  function createElectronVaultClientProxy (line 124) | function createElectronVaultClientProxy(api) {

FILE: src/hooks/useAnimatedVisibility.js
  constant SAFETY_BUFFER (line 5) | const SAFETY_BUFFER = 100

FILE: src/hooks/useAnimatedVisibility.test.js
  constant SAFETY_BUFFER (line 9) | const SAFETY_BUFFER = 100

FILE: src/hooks/useAutoLockPreferences.test.js
  constant DEFAULT_AUTO_LOCK_TIMEOUT (line 2) | const DEFAULT_AUTO_LOCK_TIMEOUT = 300000

FILE: src/hooks/useAutoLockPreferences.ts
  type AutoLockContextValue (line 17) | type AutoLockContextValue = {
  function getAutoLockTimeoutMs (line 118) | function getAutoLockTimeoutMs(): number | null {
  function isAutoLockEnabled (line 129) | function isAutoLockEnabled(): boolean {

FILE: src/hooks/useCreateOrEditRecord.d.ts
  type CreateOrEditRecordParams (line 3) | type CreateOrEditRecordParams = {

FILE: src/hooks/usePearUpdate.js
  function handleUpdateApp (line 69) | async function handleUpdateApp() {

FILE: src/hooks/useRecordActionItems.d.ts
  type RecordActionItem (line 1) | type RecordActionItem = {

FILE: src/hooks/useRecordMenuItemsV2.ts
  type IconComponent (line 19) | type IconComponent = React.ComponentType<React.SVGProps<SVGSVGElement>>
  type RecordMenuItemV2 (line 21) | type RecordMenuItemV2 = {
  constant ALL_ITEMS_TYPE (line 28) | const ALL_ITEMS_TYPE = 'all'

FILE: src/hooks/useRiveWithRetry.ts
  constant MAX_RETRIES (line 5) | const MAX_RETRIES = 20
  constant RETRY_DELAY_MS (line 6) | const RETRY_DELAY_MS = 1000
  type UseRiveWithRetryReturn (line 8) | type UseRiveWithRetryReturn = RiveState & {

FILE: src/hooks/useScrollOverflow.test.js
  function MockResizeObserver (line 7) | function MockResizeObserver(callback) {
  function MockMutationObserver (line 23) | function MockMutationObserver(callback) {

FILE: src/hooks/useScrollOverflow.ts
  constant SUBPIXEL_TOLERANCE (line 3) | const SUBPIXEL_TOLERANCE = 0.5

FILE: src/hooks/useTranslation.ts
  type TranslationValues (line 5) | type TranslationValues = Record<string, unknown>

FILE: src/hooks/useVaultSwitch.tsx
  function useVaultSwitch (line 13) | function useVaultSwitch() {

FILE: src/lib-react-components/components/InputField/index.tsx
  type InputType (line 18) | type InputType = 'text' | 'password' | 'url'
  type InputVariant (line 19) | type InputVariant = 'default' | 'outline'
  type Props (line 21) | interface Props {

FILE: src/lib-react-components/components/InputField/styles.ts
  type InputProps (line 3) | interface InputProps {

FILE: src/lib-react-components/components/PasswordField/index.js
  constant PASSWORD_STRENGTH_ICONS (line 21) | const PASSWORD_STRENGTH_ICONS = {

FILE: src/lib-react-components/components/PearPassPasswordFieldV2/types.ts
  type PearPassPasswordFieldV2Props (line 1) | type PearPassPasswordFieldV2Props = {

FILE: src/lib-react-components/components/Switch/styles.js
  constant TRANSITION_PROPERTIES (line 3) | const TRANSITION_PROPERTIES = '300ms ease-in-out'

FILE: src/lib-react-components/components/TextArea/index.tsx
  type Variant (line 12) | type Variant = 'default' | 'report'
  type Props (line 14) | interface Props {

FILE: src/lib-react-components/components/TextArea/styles.ts
  type TextAreaStyledProps (line 3) | interface TextAreaStyledProps {

FILE: src/lib-react-components/icons/icons.test.js
  function getIconModules (line 11) | function getIconModules(dir) {

FILE: src/pages/AuthenticatorView/index.js
  constant SORT_MENU_WIDTH (line 56) | const SORT_MENU_WIDTH = 260

FILE: src/pages/Intro/OnboardingLockVideo/index.tsx
  constant START_SRC (line 3) | const START_SRC = 'assets/video/onboarding_lock_start.webm'
  constant LOOP_SRC (line 4) | const LOOP_SRC = 'assets/video/onboarding_lock_loop.webm'

FILE: src/pages/LoadingPage/LoadingPageV2.tsx
  type LoadingPageV2Props (line 15) | interface LoadingPageV2Props {

FILE: src/pages/LoadingPage/LoadingPageV2Styles.ts
  type ProgressTrackProps (line 4) | interface ProgressTrackProps {
  type ProgressFillProps (line 8) | interface ProgressFillProps {

FILE: src/pages/MainView/index.js
  constant SORT_BY_TYPE (line 26) | const SORT_BY_TYPE = {

FILE: src/pages/SettingsView/AboutContent/index.js
  constant OFFLINE_TIMEOUT (line 25) | const OFFLINE_TIMEOUT = 'OFFLINE_TIMEOUT'
  constant OFFLINE_TIMEOUT_MS (line 26) | const OFFLINE_TIMEOUT_MS = 10000
  constant OFFLINE_TIMEOUT_MESSAGE (line 27) | const OFFLINE_TIMEOUT_MESSAGE =

FILE: src/pages/SettingsView/ExportTab/utils/downloadFile.test.js
  method href (line 15) | set href(val) {
  method href (line 18) | get href() {
  method download (line 21) | set download(val) {
  method download (line 24) | get download() {
  method href (line 63) | set href(val) {
  method href (line 66) | get href() {
  method download (line 69) | set download(val) {
  method download (line 72) | get download() {

FILE: src/pages/SettingsView/ExportTab/utils/downloadZip.test.js
  method href (line 19) | set href(val) {
  method href (line 22) | get href() {
  method download (line 25) | set download(val) {
  method download (line 28) | get download() {
  method href (line 75) | set href(val) {
  method href (line 78) | get href() {
  method download (line 81) | set download(val) {
  method download (line 84) | get download() {

FILE: src/pages/SettingsView/SettingsAdvancedTab/SettingsAutoLockConfiguration/index.tsx
  constant TIMEOUT_OPTIONS (line 43) | const TIMEOUT_OPTIONS = Object.entries(AUTO_LOCK_TIMEOUT_OPTIONS).map(

FILE: src/pages/SettingsView/index.js
  constant NAV_ITEMS (line 32) | const NAV_ITEMS = [

FILE: src/pages/SettingsViewV2/SettingsViewV2.tsx
  type SettingsItemKey (line 58) | enum SettingsItemKey {
  type SectionItem (line 73) | type SectionItem = {
  type Section (line 79) | type Section = {

FILE: src/pages/SettingsViewV2/content/AppPreferencesContent/index.tsx
  constant TEST_IDS (line 24) | const TEST_IDS = {
  type TimeoutOption (line 32) | type TimeoutOption = {
  constant TIMEOUT_OPTIONS (line 38) | const TIMEOUT_OPTIONS: TimeoutOption[] = Object.entries(

FILE: src/pages/SettingsViewV2/content/AppVersionContent/index.tsx
  constant TEST_IDS (line 13) | const TEST_IDS = {
  type AppVersionContentProps (line 18) | type AppVersionContentProps = {

FILE: src/pages/SettingsViewV2/content/BlindPeersContent/index.tsx
  constant TEST_IDS (line 25) | const TEST_IDS = {
  function getMirrorsSnapshotKey (line 34) | function getMirrorsSnapshotKey(

FILE: src/pages/SettingsViewV2/content/DiagnosticsContent/index.test.tsx
  constant TEST_IDS (line 86) | const TEST_IDS = {
  function withElectronAPI (line 92) | function withElectronAPI(value: unknown, run: () => Promise<void> | void) {

FILE: src/pages/SettingsViewV2/content/DiagnosticsContent/index.tsx
  constant TEST_IDS (line 15) | const TEST_IDS = {

FILE: src/pages/SettingsViewV2/content/ExportItemsContent/index.tsx
  type FormValues (line 31) | type FormValues = {
  type ExportFormat (line 36) | enum ExportFormat {

FILE: src/pages/SettingsViewV2/content/ImportCodesContent/index.tsx
  type ImportCodesOption (line 12) | type ImportCodesOption = {

FILE: src/pages/SettingsViewV2/content/ImportItemsContent/index.tsx
  type ImportState (line 44) | type ImportState = 'default' | 'upload' | 'inputPassword'
  type ImportOption (line 46) | type ImportOption = {
  type FileInfo (line 56) | type FileInfo = {
  type ImportOptionType (line 64) | enum ImportOptionType {

FILE: src/pages/SettingsViewV2/content/LanguageContent/index.tsx
  type LanguageOption (line 18) | type LanguageOption = {
  constant TEST_IDS (line 24) | const TEST_IDS = {

FILE: src/pages/SettingsViewV2/content/MasterPasswordContent/index.tsx
  constant STRENGTH_MAP (line 29) | const STRENGTH_MAP: Record<string, PasswordIndicatorVariant> = {

FILE: src/pages/SettingsViewV2/content/ReportAProblemContent/index.test.tsx
  constant TEST_IDS (line 125) | const TEST_IDS = {

FILE: src/pages/SettingsViewV2/content/ReportAProblemContent/index.tsx
  constant OFFLINE_TIMEOUT_MS (line 27) | const OFFLINE_TIMEOUT_MS = 10000
  constant OFFLINE_TIMEOUT_MESSAGE (line 28) | const OFFLINE_TIMEOUT_MESSAGE =
  constant TIMED_OUT (line 30) | const TIMED_OUT = Symbol('feedback_timed_out')
  constant TEST_IDS (line 32) | const TEST_IDS = {
  type ReportAProblemContentProps (line 38) | type ReportAProblemContentProps = {

FILE: src/pages/SettingsViewV2/content/YourDevicesContent/index.tsx
  constant TEST_IDS (line 20) | const TEST_IDS = {

FILE: src/pages/SettingsViewV2/content/YourVaultsContent/styles.ts
  constant VAULT_LOCK_ICON_BACKGROUND (line 4) | const VAULT_LOCK_ICON_BACKGROUND = 'rgba(176, 217, 68, 0.18)'

FILE: src/pages/WelcomePage/LockedScreenV2/LockedScreenV2.test.tsx
  type MasterPasswordLockStatus (line 13) | type MasterPasswordLockStatus = {

FILE: src/pages/WelcomePage/LockedScreenV2/LockedScreenV2.tsx
  type LockCountdownProps (line 14) | type LockCountdownProps = {

FILE: src/pages/WelcomePage/index.js
  constant V2_STATES (line 22) | const V2_STATES = new Set([

FILE: src/services/createOrGetPearpassClient.js
  function createOrGetPearpassClient (line 11) | function createOrGetPearpassClient(ipc, storagePath, opts = {}) {

FILE: src/services/createOrGetPipe.js
  constant WORKLET_PATH_DEV (line 6) | const WORKLET_PATH_DEV =
  constant WORKLET_PATH_PROD (line 8) | const WORKLET_PATH_PROD =

FILE: src/services/handlers/EncryptionHandlers.js
  class EncryptionHandlers (line 9) | class EncryptionHandlers {
    method constructor (line 10) | constructor(client) {
    method encryptionInit (line 14) | async encryptionInit() {
    method encryptionGetStatus (line 19) | async encryptionGetStatus() {
    method encryptionGet (line 23) | async encryptionGet(params) {
    method encryptionAdd (line 29) | async encryptionAdd(params) {
    method hashPassword (line 34) | async hashPassword(params) {
    method encryptVaultKeyWithHashedPassword (line 38) | async encryptVaultKeyWithHashedPassword(params) {
    method encryptVaultWithKey (line 44) | async encryptVaultWithKey(params) {
    method getDecryptionKey (line 51) | async getDecryptionKey(params) {
    method decryptVaultKey (line 61) | async decryptVaultKey(params) {
    method recordFailedMasterPassword (line 72) | async recordFailedMasterPassword() {
    method getMasterPasswordStatus (line 77) | async getMasterPasswordStatus() {
    method resetFailedAttempts (line 87) | async resetFailedAttempts() {
    method initWithPassword (line 92) | async initWithPassword(params) {

FILE: src/services/handlers/SecureRequestHandler.js
  class SecureRequestHandler (line 14) | class SecureRequestHandler {
    method constructor (line 15) | constructor(client, methodRegistry) {
    method handle (line 23) | async handle(params) {
    method validateSecurePayload (line 52) | validateSecurePayload(sessionId, nonceB64, ciphertextB64) {
    method validateSession (line 63) | async validateSession(sessionId, seq) {
    method decryptRequest (line 85) | async decryptRequest(sessionId, nonceB64, ciphertextB64) {
    method encryptResponse (line 92) | async encryptResponse(sessionId, result) {

FILE: src/services/handlers/SecurityHandlers.js
  class SecurityHandlers (line 37) | class SecurityHandlers {
    method constructor (line 38) | constructor(client) {
    method nmGetAppIdentity (line 45) | async nmGetAppIdentity(params) {
    method nmConfirmPairing (line 123) | async nmConfirmPairing(params) {
    method nmBeginHandshake (line 144) | async nmBeginHandshake(params) {
    method nmFinishHandshake (line 182) | async nmFinishHandshake(params) {
    method nmCloseSession (line 286) | async nmCloseSession(params) {
    method checkExtensionPairingStatus (line 305) | async checkExtensionPairingStatus(params) {
    method getAutoLockSettings (line 334) | async getAutoLockSettings() {
    method setAutoLockTimeout (line 349) | async setAutoLockTimeout(params) {
    method setAutoLockEnabled (line 378) | async setAutoLockEnabled(params) {
    method resetTimer (line 400) | async resetTimer() {
    method nmResetPairing (line 418) | async nmResetPairing() {

FILE: src/services/handlers/VaultHandlers.js
  class VaultHandlers (line 7) | class VaultHandlers {
    method constructor (line 8) | constructor(client) {
    method vaultsInit (line 12) | async vaultsInit(params) {
    method vaultsGetStatus (line 24) | async vaultsGetStatus() {
    method vaultsGet (line 28) | async vaultsGet(params) {
    method vaultsList (line 32) | async vaultsList(params) {
    method vaultsAdd (line 61) | async vaultsAdd(params) {
    method vaultsClose (line 66) | async vaultsClose() {
    method activeVaultInit (line 71) | async activeVaultInit(params) {
    method loadVaultMetadata (line 85) | async loadVaultMetadata(vaultId) {
    method activeVaultGetStatus (line 109) | async activeVaultGetStatus() {
    method activeVaultGet (line 113) | async activeVaultGet(params) {
    method activeVaultList (line 117) | async activeVaultList(params) {
    method activeVaultAdd (line 135) | async activeVaultAdd(params) {
    method activeVaultRemove (line 140) | async activeVaultRemove(params) {
    method activeVaultClose (line 145) | async activeVaultClose() {
    method activeVaultCreateInvite (line 150) | async activeVaultCreateInvite() {
    method activeVaultDeleteInvite (line 154) | async activeVaultDeleteInvite() {
    method pairActiveVault (line 159) | async pairActiveVault(params) {
    method initListener (line 163) | async initListener(params) {
    method closeAllInstances (line 168) | async closeAllInstances() {
    method cancelPairActiveVault (line 205) | async cancelPairActiveVault(params) {
    method activeVaultRemoveFile (line 209) | async activeVaultRemoveFile(params) {
    method fetchFavicon (line 214) | async fetchFavicon(params) {
    method generateOtpCodesByIds (line 218) | async generateOtpCodesByIds(params) {
    method generateHotpNext (line 222) | async generateHotpNext(params) {
    method addOtpToRecord (line 226) | async addOtpToRecord(params) {
    method removeOtpFromRecord (line 231) | async removeOtpFromRecord(params) {

FILE: src/services/ipc/MethodRegistry.js
  class MethodRegistry (line 8) | class MethodRegistry {
    method constructor (line 12) | constructor(wrapperFn = null) {
    method register (line 24) | register(name, handler, config = {}) {
    method execute (line 41) | async execute(methodName, params, context) {
    method performStatusChecks (line 165) | async performStatusChecks(methodName, statusChecks, context) {
    method getMethodNames (line 194) | getMethodNames() {
    method hasMethod (line 201) | hasMethod(name) {

FILE: src/services/ipc/SocketManager.js
  class SocketManager (line 16) | class SocketManager {
    method constructor (line 17) | constructor(socketName) {
    method getSocketPath (line 25) | getSocketPath(socketName) {
    method cleanupSocket (line 36) | async cleanupSocket() {
    method ensureSocketDir (line 55) | async ensureSocketDir() {
    method getPath (line 63) | getPath() {

FILE: src/services/nativeMessagingIPCServer.js
  class NativeMessagingIPCServer (line 18) | class NativeMessagingIPCServer {
    method constructor (line 22) | constructor(pearpassClient) {
    method setupHandlers (line 56) | setupHandlers() {
    method emitIPCActivity (line 136) | emitIPCActivity() {
    method registerSecureMethods (line 146) | registerSecureMethods(encryptionHandlers, vaultHandlers) {
    method start (line 330) | async start() {
    method stop (line 405) | async stop() {

FILE: src/services/security/appIdentity.js
  constant ENC_KEY_ED25519 (line 14) | const ENC_KEY_ED25519 = 'nm.identity.ed25519'
  constant ENC_KEY_X25519 (line 15) | const ENC_KEY_X25519 = 'nm.identity.x25519'
  constant ENC_KEY_CREATION_DATE (line 16) | const ENC_KEY_CREATION_DATE = 'nm.identity.creationDate'
  constant ENC_KEY_CLIENT_DATA (line 17) | const ENC_KEY_CLIENT_DATA = 'nm.client.data'
  constant ENC_KEY_PAIRING_SECRET (line 18) | const ENC_KEY_PAIRING_SECRET = 'nm.identity.pairingSecret'
  constant PAIRING_CODE_TAG (line 19) | const PAIRING_CODE_TAG = Buffer.from('pearpass/pairingcode/v1', 'utf8')
  constant MEMORY_IDENTITY (line 23) | let MEMORY_IDENTITY = null
  constant ED25519_SECRETKEY_BYTES (line 28) | const ED25519_SECRETKEY_BYTES =
  constant ED25519_PUBLICKEY_BYTES (line 30) | const ED25519_PUBLICKEY_BYTES =
  constant X25519_SECRETKEY_BYTES (line 32) | const X25519_SECRETKEY_BYTES =
  constant X25519_PUBLICKEY_BYTES (line 34) | const X25519_PUBLICKEY_BYTES =

FILE: src/services/security/protocolConstants.js
  constant PROTOCOL_TAGS (line 5) | const PROTOCOL_TAGS = {

FILE: src/services/security/sessionManager.js
  function finalizeHandshakeWithMemoryIdentity (line 199) | function finalizeHandshakeWithMemoryIdentity(

FILE: src/services/security/sessionStore.js
  constant SESSIONS (line 9) | const SESSIONS = new Map()
  constant SESSION_TTL_MS (line 12) | const SESSION_TTL_MS = 60 * 60 * 1000

FILE: src/shared/commandDefinitions.js
  constant COMMAND_DEFINITIONS (line 12) | const COMMAND_DEFINITIONS = [
  constant COMMAND_NAMES (line 83) | const COMMAND_NAMES = COMMAND_DEFINITIONS.map((cmd) => cmd.name)

FILE: src/shared/types.ts
  type VaultRecord (line 1) | type VaultRecord = {
  type PassType (line 15) | enum PassType {

FILE: src/svgs/ItemCardIllustration/ItemCardIllustration.tsx
  type ItemCardIllustrationProps (line 3) | type ItemCardIllustrationProps = {

FILE: src/types/electron.d.ts
  type Window (line 4) | interface Window {

FILE: src/types/jest-globals.d.ts
  type Matchers (line 21) | interface Matchers<R = void> {

FILE: src/types/modules.d.ts
  type Vault (line 12) | interface Vault {
  type VaultDevice (line 19) | interface VaultDevice {
  type UseVaultsResult (line 25) | interface UseVaultsResult {
  type UseVaultResult (line 40) | interface UseVaultResult {
  type FolderRecord (line 174) | interface FolderRecord {
  type FolderEntry (line 182) | interface FolderEntry {
  type FoldersData (line 187) | interface FoldersData {
  type UseFoldersResult (line 193) | interface UseFoldersResult {
  type UseCreateFolderResult (line 204) | interface UseCreateFolderResult {
  type PasswordStrengthType (line 241) | type PasswordStrengthType = 'error' | 'warning' | 'success'
  type FeedbackTopic (line 389) | type FeedbackTopic =
  type FeedbackApp (line 393) | type FeedbackApp = 'MOBILE' | 'DESKTOP' | 'BROWSER_EXTENSION'
  type SlackFeedbackPayload (line 395) | interface SlackFeedbackPayload {
  type GoogleFormMapping (line 407) | interface GoogleFormMapping {
  type GoogleFormFeedbackPayload (line 417) | interface GoogleFormFeedbackPayload {

FILE: src/types/styled.d.ts
  type DefaultTheme (line 4) | interface DefaultTheme {

FILE: src/utils/applyGlobalStyles.js
  function applyGlobalStyles (line 1) | function applyGlobalStyles(styles) {

FILE: src/utils/autoLock.js
  function applyAutoLockEnabled (line 5) | function applyAutoLockEnabled(enabled) {
  function applyAutoLockTimeout (line 18) | function applyAutoLockTimeout(ms) {

FILE: src/utils/createErrorWithCode.js
  function createErrorWithCode (line 7) | function createErrorWithCode(code, message) {

FILE: src/utils/devicePreferences.cjs
  constant FILE_NAME (line 13) | const FILE_NAME = 'device-preferences.json'
  constant DEFAULTS (line 15) | const DEFAULTS = {
  function read (line 19) | function read(storageDir) {
  function write (line 31) | function write(storageDir, partial) {

FILE: src/utils/extractDomainName.js
  function extractDomainName (line 5) | function extractDomainName(url) {

FILE: src/utils/getPasswordStrengthInfo.ts
  type StrengthResult (line 7) | type StrengthResult = {
  function computeStrengthResult (line 13) | function computeStrengthResult(
  function getPasswordStrength (line 27) | function getPasswordStrength(

FILE: src/utils/getRecordSubtitle.ts
  type RecordLike (line 1) | type RecordLike = {

FILE: src/utils/groupRecordsByTimePeriod.test.ts
  constant SECOND (line 6) | const SECOND = 1000
  constant MINUTE (line 7) | const MINUTE = 60 * SECOND
  constant HOUR (line 8) | const HOUR = 60 * MINUTE
  constant DAY (line 9) | const DAY = 24 * HOUR

FILE: src/utils/groupRecordsByTimePeriod.ts
  type VaultRecord (line 1) | type VaultRecord = {
  type RecordSort (line 20) | type RecordSort = {
  type RecordSection (line 25) | type RecordSection = {

FILE: src/utils/isFavorite.js
  constant FAVORITES_FOLDER_ID (line 4) | const FAVORITES_FOLDER_ID = 'favorites'

FILE: src/utils/logHelper.cjs
  constant LEVELS (line 24) | const LEVELS = { debug: 0, info: 1, warn: 2, error: 3 }
  constant MAX_LOG_FILE_SIZE (line 25) | const MAX_LOG_FILE_SIZE = 100 * 1024 * 1024
  constant TRUNCATION_KEEP_RATIO (line 26) | const TRUNCATION_KEEP_RATIO = 0.5
  constant TRUNCATION_CHECK_INTERVAL (line 27) | const TRUNCATION_CHECK_INTERVAL = 100
  function getLogPaths (line 29) | function getLogPaths(storageDir) {
  function removeLogFiles (line 38) | function removeLogFiles(storageDir) {
  function createWorkletLogConfigurer (line 49) | function createWorkletLogConfigurer({
  function createMainProcessLogger (line 77) | function createMainProcessLogger(options = {}) {
  function setupLogging (line 193) | function setupLogging({ app, pkg, debugMode, getStorageDir, getVaultClie...

FILE: src/utils/logger.js
  class Logger (line 3) | class Logger {
    method constructor (line 4) | constructor({ debugMode = false } = {}) {
    method _print (line 14) | _print(level, component, message) {
    method log (line 35) | log(component, ...args) {
    method debug (line 44) | debug(component, ...args) {
    method info (line 53) | info(component, ...args) {
    method warn (line 62) | warn(component, ...args) {
    method error (line 71) | error(component, ...args) {

FILE: src/utils/nativeMessagingSetup.js
  constant FLATPAK_APP_ID (line 19) | const FLATPAK_APP_ID = 'com.pears.pass'
  constant FLATPAK_NATIVE_HOST_COMMAND (line 20) | const FLATPAK_NATIVE_HOST_COMMAND = 'pearpass-native-host'
  constant NATIVE_BRIDGE_PROCESS_IDENTIFIER (line 22) | const NATIVE_BRIDGE_PROCESS_IDENTIFIER = 'pearpass-lib-native-messaging-...

FILE: src/utils/nativeMessagingSetup.test.js
  constant MOCK_USER_DATA_PATH (line 33) | const MOCK_USER_DATA_PATH = '/mock/userData'
  constant MOCK_EXEC_PATH (line 34) | const MOCK_EXEC_PATH = '/mock/electron/PearPass'
  constant MOCK_BRIDGE_PATH (line 35) | const MOCK_BRIDGE_PATH = '/mock/dist/native-messaging-bridge.bundle.cjs'

FILE: src/utils/vaultCreated.js
  function vaultCreatedFormat (line 8) | function vaultCreatedFormat(vaultCreatedDate) {

FILE: src/utils/withAlpha.ts
  constant HEX_COLOR (line 1) | const HEX_COLOR = /^#([0-9a-fA-F]{6})$/
Condensed preview — 917 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (4,478K chars).
[
  {
    "path": ".claude/skills/use-ui-kit/SKILL.md",
    "chars": 14560,
    "preview": "---\nname: use-ui-kit\ndescription: Use whenever creating or editing UI in this repo — React components, modals, dialogs, "
  },
  {
    "path": ".cursor/rules/use-ui-kit.mdc",
    "chars": 13744,
    "preview": "---\ndescription: Rules for building and editing UI in this repo. The repo uses @tetherto/pearpass-lib-ui-kit as the sing"
  },
  {
    "path": ".github/actions/authorize-pr/action.yml",
    "chars": 5116,
    "preview": "name: Authorize PR\ndescription: >\n  Security gate for pull_request_target workflows only. Checks actor\n  write permissio"
  },
  {
    "path": ".github/pull_request_template.md",
    "chars": 441,
    "preview": "### Requirements\n<!-- List the requirements for this PR -->\n\n### Changes\n<!-- Summarize the changes introduced in this P"
  },
  {
    "path": ".github/workflows/ci-pr.yml",
    "chars": 1337,
    "preview": "name: PR CI\n\non:\n  pull_request_target:\n    types: [opened, reopened, synchronize, labeled]\n    branches: [main]\n\npermis"
  },
  {
    "path": ".github/workflows/ci-push.yml",
    "chars": 314,
    "preview": "name: Push CI\n\non:\n  push:\n    branches: [main]\n\npermissions:\n  contents: read\n\nconcurrency:\n  group: push-ci-${{ github"
  },
  {
    "path": ".github/workflows/ci-reusable.yml",
    "chars": 1188,
    "preview": "name: Reusable CI\n\non:\n  workflow_call:\n    inputs:\n      checkout_ref:\n        description: Git ref or SHA to validate\n"
  },
  {
    "path": ".gitignore",
    "chars": 810,
    "preview": "node_modules\n\nsrc/locales\n\n.yalc\nyalc.lock\npass\n\n#ignore IntelliJ or other Jetbrains files\n.idea\n\n# Generated log files\n"
  },
  {
    "path": ".husky/pre-commit",
    "chars": 31,
    "preview": "npm run lint\nnpm run build:app\n"
  },
  {
    "path": ".npmrc",
    "chars": 46,
    "preview": "foreground-scripts=true\nlegacy-peer-deps=true\n"
  },
  {
    "path": ".nvmrc",
    "chars": 7,
    "preview": "22.12.0"
  },
  {
    "path": "AGENTS.md",
    "chars": 13892,
    "preview": "# UI conventions for pearpass-app-desktop-tether\n\nThis is the Electron desktop app for PearPass. It's written in React +"
  },
  {
    "path": "CLAUDE.md",
    "chars": 2231,
    "preview": "# pearpass-app-desktop-tether\n\n## UI: always use `@tetherto/pearpass-lib-ui-kit`\n\nUI is built on `@tetherto/pearpass-lib"
  },
  {
    "path": "CONTRIBUTING.md",
    "chars": 778,
    "preview": "# Contributing to PearPass\n\nThe source is open to use as per the [LICENSE](./LICENSE).\n \nBe aware that **any pull-reques"
  },
  {
    "path": "LICENSE.md",
    "chars": 10455,
    "preview": "# License\n\nThis project is licensed under the Apache License, Version 2.0.\nSee below for the full license text.\n\n## Apac"
  },
  {
    "path": "NOTICE.md",
    "chars": 550,
    "preview": "Copyright 2026 Tether Inc\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file exc"
  },
  {
    "path": "README.md",
    "chars": 7862,
    "preview": "<p align=\"center\">\n  <img src=\"assets/images/logo.png\" alt=\"Pearpass logo\" width=\"264\"/>\n</p>\n\n# PearPass Desktop\n\n> The"
  },
  {
    "path": "SECURITY.md",
    "chars": 282,
    "preview": "# Report a security issue\n\nTo report a security issue, please email [security-oss@tether.io](mailto:security-oss@tether."
  },
  {
    "path": "app.electron.tsx",
    "chars": 3801,
    "preview": "/**\n * Electron-only entry for the renderer bundle (nodeIntegration: true).\n */\nimport { i18n } from '@lingui/core'\nimpo"
  },
  {
    "path": "appling/README.md",
    "chars": 2154,
    "preview": "# PearPass Appling\n\nPear Appling installer for PearPass Desktop application.\n\n## Overview\n\nThis is a self-contained inst"
  },
  {
    "path": "appling/app.cjs",
    "chars": 177,
    "preview": "const { install } = require('./lib/install')\n\n// When we have pear://pearpass, use this\n// install(\"pearpass\");\n\ninstall"
  },
  {
    "path": "appling/app.dev.cjs",
    "chars": 156,
    "preview": "const { install } = require('./lib/install')\n\n// NOTE: Change the key when dev key changes\n\ninstall('8ue4k8ooakpmwukzutk"
  },
  {
    "path": "appling/app.staging.cjs",
    "chars": 160,
    "preview": "const { install } = require('./lib/install')\n\n// NOTE: Change the key when staging key changes\n\ninstall('8k5z91c8u7nycsj"
  },
  {
    "path": "appling/entitlements.plist",
    "chars": 2262,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/P"
  },
  {
    "path": "appling/lib/install.cjs",
    "chars": 2909,
    "preview": "const Thread = require(\"bare-thread\");\nconst { App, Screen, Window, WebView } = require(\"fx-native\");\nconst appling = re"
  },
  {
    "path": "appling/lib/preflight.cjs",
    "chars": 1292,
    "preview": "const appling = require('appling-native')\n\n/**\n * Performs pre-installation checks to determine if the app is already in"
  },
  {
    "path": "appling/lib/progress.cjs",
    "chars": 2298,
    "preview": "const prettyBytes = require('prettier-bytes')\n\nconst { encode } = require('./utils')\n\n/**\n * Multi-stage progress tracke"
  },
  {
    "path": "appling/lib/utils.cjs",
    "chars": 1692,
    "preview": "const prettyBytes = require('prettier-bytes')\n\n/**\n * Encodes an object to JSON string for IPC messaging.\n * @param {obj"
  },
  {
    "path": "appling/lib/view.html.cjs",
    "chars": 13085,
    "preview": "const AUTO_LAUNCH = true\nconst SLOW_TIMEOUT = 180000 // 3 minutes\n\n// Simple inline SVG for splash - can be replaced wit"
  },
  {
    "path": "appling/lib/worker.cjs",
    "chars": 2859,
    "preview": "const appling = require('appling-native')\nconst { App } = require('fx-native')\nconst bootstrap = require('pear-updater-b"
  },
  {
    "path": "appling/package.json",
    "chars": 951,
    "preview": "{\n  \"private\": true,\n  \"name\": \"PearPass\",\n  \"version\": \"1.0.0\",\n  \"description\": \"Pear Appling for PearPass\",\n  \"engine"
  },
  {
    "path": "babel.config.cjs",
    "chars": 798,
    "preview": "module.exports = {\n  presets: [\n    [\n      '@babel/preset-env',\n      {\n        targets: { node: 'current' },\n        m"
  },
  {
    "path": "babel.strict-dom.cjs",
    "chars": 340,
    "preview": "const dev = process.env.NODE_ENV !== 'production'\n\nmodule.exports = {\n  babelrc: false,\n  configFile: false,\n  parserOpt"
  },
  {
    "path": "build-assets/win/AppxManifest.xml",
    "chars": 2487,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<Package\n  xmlns=\"http://schemas.microsoft.com/appx/manifest/foundation/windows10"
  },
  {
    "path": "docs/Electron-Packaging-And-Runtime.md",
    "chars": 11527,
    "preview": "# Electron packaging and runtime\n\nThis document describes how the PearPass desktop app is built, packaged, and how the m"
  },
  {
    "path": "e2e/.gitignore",
    "chars": 64,
    "preview": "test-artifacts/\nnode_modules/\nplaywright-report/\n.DS_Store\n.env\n"
  },
  {
    "path": "e2e/components/CreateOrEditPage.js",
    "chars": 8191,
    "preview": "import { test, expect } from '../fixtures/app.runner.js'\n\nclass CreateOrEditPage {\n  constructor(root) {\n    this.root ="
  },
  {
    "path": "e2e/components/DetailsPage.js",
    "chars": 9740,
    "preview": "import { test, expect } from '../fixtures/app.runner.js'\n\nclass DetailsPage {\n  constructor(root) {\n    this.root = root"
  },
  {
    "path": "e2e/components/LoginPage.js",
    "chars": 986,
    "preview": "import { test, expect } from '../fixtures/app.runner.js'\n\nclass LoginPage {\n  constructor(root) {\n    this.root = root\n "
  },
  {
    "path": "e2e/components/MainPage.js",
    "chars": 6221,
    "preview": "import { test, expect } from '../fixtures/app.runner.js'\n\nclass MainPage {\n  constructor(root) {\n    this.root = root\n  "
  },
  {
    "path": "e2e/components/SettingsPage.js",
    "chars": 1825,
    "preview": "import { test, expect } from '../fixtures/app.runner.js'\n\nclass SettingsPage {\n  constructor(root) {\n    this.root = roo"
  },
  {
    "path": "e2e/components/SideMenuPage.js",
    "chars": 3491,
    "preview": "import { test, expect } from '../fixtures/app.runner.js'\n\nclass SideMenuPage {\n  constructor(root) {\n    this.root = roo"
  },
  {
    "path": "e2e/components/Utilities.js",
    "chars": 1593,
    "preview": "import { test, expect } from '../fixtures/app.runner.js'\n\nclass Utilities {\n  constructor(root) {\n    this.root = root\n "
  },
  {
    "path": "e2e/components/index.js",
    "chars": 329,
    "preview": "export { LoginPage } from './LoginPage.js'\nexport { CreateOrEditPage } from './CreateOrEditPage.js'\nexport { DetailsPage"
  },
  {
    "path": "e2e/fixtures/app.runner.js",
    "chars": 6454,
    "preview": "import { spawn } from 'node:child_process'\nimport { createRequire } from 'node:module'\nimport os from 'node:os'\nimport p"
  },
  {
    "path": "e2e/fixtures/test-data.js",
    "chars": 648,
    "preview": "'use strict'\n\n/**\n * Centralized test data for reuse across test suites\n */\nmodule.exports = {\n  // User credentials\n  c"
  },
  {
    "path": "e2e/package.json",
    "chars": 452,
    "preview": "{\n  \"name\": \"pearpass-e2e\",\n  \"version\": \"1.0.0\",\n  \"scripts\": {\n    \"test\": \"npx playwright test\",\n    \"test:headed\": \""
  },
  {
    "path": "e2e/playwright.config.js",
    "chars": 1232,
    "preview": "import { defineConfig } from '@playwright/test'\nimport dotenv from 'dotenv'\n\ndotenv.config()\n\nexport default defineConfi"
  },
  {
    "path": "e2e/scripts/explore.js",
    "chars": 1033,
    "preview": "'use strict'\nconst { chromium } = require('@playwright/test')\n\nasync function explore() {\n  const browser = await chromi"
  },
  {
    "path": "e2e/specs/01-Login/creatingLoginItem.test.js",
    "chars": 8542,
    "preview": "import { qase } from 'playwright-qase-reporter'\n\nimport {\n  LoginPage,\n  MainPage,\n  SideMenuPage,\n  CreateOrEditPage,\n "
  },
  {
    "path": "e2e/specs/01-Login/creatingLoginItemPassword.test.js",
    "chars": 3776,
    "preview": "import { qase } from 'playwright-qase-reporter'\n\nimport {\n  LoginPage,\n  MainPage,\n  SideMenuPage,\n  CreateOrEditPage,\n "
  },
  {
    "path": "e2e/specs/01-Login/editingDeletingLoginItem.test.js",
    "chars": 4726,
    "preview": "import { qase } from 'playwright-qase-reporter'\n\nimport {\n  LoginPage,\n  MainPage,\n  SideMenuPage,\n  CreateOrEditPage,\n "
  },
  {
    "path": "e2e/specs/02-CreditCard/creatingCreditCardItem.test.js",
    "chars": 7973,
    "preview": "import { qase } from 'playwright-qase-reporter'\n\nimport {\n  LoginPage,\n  MainPage,\n  SideMenuPage,\n  CreateOrEditPage,\n "
  },
  {
    "path": "e2e/specs/02-CreditCard/editingDeleteCreditCardItem.test.js",
    "chars": 4906,
    "preview": "import { qase } from 'playwright-qase-reporter'\n\nimport {\n  LoginPage,\n  MainPage,\n  SideMenuPage,\n  CreateOrEditPage,\n "
  },
  {
    "path": "e2e/specs/03-WiFi/creatingWiFiItem.test.js",
    "chars": 5962,
    "preview": "import { qase } from 'playwright-qase-reporter'\n\nimport {\n  LoginPage,\n  MainPage,\n  SideMenuPage,\n  CreateOrEditPage,\n "
  },
  {
    "path": "e2e/specs/03-WiFi/editingDeletingWiFiItem.test.js",
    "chars": 3000,
    "preview": "import { qase } from 'playwright-qase-reporter'\n\nimport {\n  LoginPage,\n  MainPage,\n  SideMenuPage,\n  CreateOrEditPage,\n "
  },
  {
    "path": "e2e/specs/04-Identity/creatingIdentityItem.test.js",
    "chars": 11111,
    "preview": "import { qase } from 'playwright-qase-reporter'\n\nimport {\n  LoginPage,\n  MainPage,\n  SideMenuPage,\n  CreateOrEditPage,\n "
  },
  {
    "path": "e2e/specs/04-Identity/editingDeletingIdentityItem.test.js",
    "chars": 8526,
    "preview": "import { qase } from 'playwright-qase-reporter'\n\nimport {\n  LoginPage,\n  MainPage,\n  SideMenuPage,\n  CreateOrEditPage,\n "
  },
  {
    "path": "e2e/specs/05-PassPhrase/creatingPassPhraseItem.test.js",
    "chars": 5253,
    "preview": "import clipboard from 'clipboardy'\nimport { qase } from 'playwright-qase-reporter'\n\nimport {\n  LoginPage,\n  MainPage,\n  "
  },
  {
    "path": "e2e/specs/05-PassPhrase/editingDeletingPassPhraseItem.test.js",
    "chars": 3501,
    "preview": "import clipboard from 'clipboardy'\nimport { qase } from 'playwright-qase-reporter'\n\nimport {\n  LoginPage,\n  MainPage,\n  "
  },
  {
    "path": "e2e/specs/06-Note/creatingNoteItem.test.js",
    "chars": 6074,
    "preview": "import { qase } from 'playwright-qase-reporter'\n\nimport {\n  LoginPage,\n  MainPage,\n  SideMenuPage,\n  CreateOrEditPage,\n "
  },
  {
    "path": "e2e/specs/06-Note/editingDeleteNoteItem.test.js",
    "chars": 2913,
    "preview": "import { qase } from 'playwright-qase-reporter'\n\nimport {\n  LoginPage,\n  MainPage,\n  SideMenuPage,\n  CreateOrEditPage,\n "
  },
  {
    "path": "e2e/specs/07-CustomField/creatingCustomFieldItem.test.js",
    "chars": 5810,
    "preview": "import { qase } from 'playwright-qase-reporter'\n\nimport {\n  LoginPage,\n  MainPage,\n  SideMenuPage,\n  CreateOrEditPage,\n "
  },
  {
    "path": "e2e/specs/07-CustomField/editingDeleteCustomFieldItem.test.js",
    "chars": 3400,
    "preview": "import { qase } from 'playwright-qase-reporter'\n\nimport {\n  LoginPage,\n  MainPage,\n  SideMenuPage,\n  CreateOrEditPage,\n "
  },
  {
    "path": "e2e/specs/08-MultipleSelection/multipleSelection.test.js",
    "chars": 6682,
    "preview": "import { qase } from 'playwright-qase-reporter'\n\nimport {\n  LoginPage,\n  MainPage,\n  SideMenuPage,\n  CreateOrEditPage,\n "
  },
  {
    "path": "e2e/specs/09-SortingAndFiltering/sorting.test.js",
    "chars": 4704,
    "preview": "import { qase } from 'playwright-qase-reporter'\n\nimport {\n  LoginPage,\n  MainPage,\n  SideMenuPage,\n  CreateOrEditPage,\n "
  },
  {
    "path": "e2e/specs/10-Settings/settings.test.js",
    "chars": 3902,
    "preview": "import { qase } from 'playwright-qase-reporter'\n\nimport {\n  LoginPage,\n  MainPage,\n  SideMenuPage,\n  CreateOrEditPage,\n "
  },
  {
    "path": "electron/clipboardCleanup.cjs",
    "chars": 3526,
    "preview": "const { spawn } = require('child_process')\nconst crypto = require('crypto')\nconst fs = require('fs')\nconst path = requir"
  },
  {
    "path": "electron/clipboardCleanup.test.js",
    "chars": 1997,
    "preview": "/* eslint-env jest */\n\njest.mock('fs', () => ({\n  existsSync: jest.fn(() => true),\n  readFileSync: jest.fn(),\n  unlinkSy"
  },
  {
    "path": "electron/clipboardCleanup.windows.ps1",
    "chars": 1368,
    "preview": "param(\n  [string]$SecretPath,\n  [string]$StatePath,\n  [string]$Token,\n  [int]$DelayMs\n)\n\ntry {\n  $expected = ''\n\n  try {"
  },
  {
    "path": "electron/clipboardCleanupHelper.cjs",
    "chars": 4383,
    "preview": "const { spawnSync } = require('child_process')\nconst fs = require('fs')\n\nconst linuxWaylandClipboard = require('./linuxW"
  },
  {
    "path": "electron/clipboardCleanupHelper.test.js",
    "chars": 4208,
    "preview": "/* eslint-env jest */\n\njest.mock('fs', () => ({\n  readFileSync: jest.fn(),\n  unlinkSync: jest.fn(),\n  existsSync: jest.f"
  },
  {
    "path": "electron/flatpak-paths.cjs",
    "chars": 2647,
    "preview": "const fs = require('fs')\nconst path = require('path')\n\nfunction isFlatpakRuntime(options = {}) {\n  const env = options.e"
  },
  {
    "path": "electron/flatpak-paths.test.js",
    "chars": 2231,
    "preview": "const {\n  getFlatpakCompatRoots,\n  getSandboxSafePath,\n  getSnapRealHome,\n  isFlatpakRuntime,\n  isSnapRuntime,\n  mapFlat"
  },
  {
    "path": "electron/linuxWaylandClipboard.cjs",
    "chars": 1679,
    "preview": "const { spawnSync } = require('child_process')\n\nconst {\n  readClipboardWithFallback,\n  clearClipboardWithFallback\n} = re"
  },
  {
    "path": "electron/linuxWaylandClipboardFallback.cjs",
    "chars": 3515,
    "preview": "const { spawnSync } = require('child_process')\nconst fs = require('fs')\nconst os = require('os')\nconst path = require('p"
  },
  {
    "path": "electron/linuxX11Clipboard.cjs",
    "chars": 1101,
    "preview": "const { spawnSync } = require('child_process')\n\nconst {\n  readClipboardWithFallback,\n  clearClipboardWithFallback\n} = re"
  },
  {
    "path": "electron/linuxX11ClipboardFallback.cjs",
    "chars": 3778,
    "preview": "const { spawnSync } = require('child_process')\nconst fs = require('fs')\nconst os = require('os')\nconst path = require('p"
  },
  {
    "path": "electron/main.cjs",
    "chars": 22318,
    "preview": "/* eslint-disable no-unused-vars */\n/* eslint-disable no-underscore-dangle */\n/**\n * Electron main process: creates the "
  },
  {
    "path": "electron/preload.cjs",
    "chars": 2063,
    "preview": "/* eslint-disable no-underscore-dangle */\n/**\n * Preload: with contextIsolation false, runs in the same context as the p"
  },
  {
    "path": "electron/preload.test.js",
    "chars": 5006,
    "preview": "/* eslint-disable no-underscore-dangle */\n/* eslint-env jest */\n\nconst path = require('path')\n\njest.mock('electron', () "
  },
  {
    "path": "electron/runtime-config.cjs",
    "chars": 792,
    "preview": "/**\n * Pear runtime config for P2P OTA updates.\n */\nconst fs = require('fs')\nconst path = require('path')\n\nconst pkg = r"
  },
  {
    "path": "electron-builder.linux.json",
    "chars": 734,
    "preview": "{\n  \"appId\": \"com.pears.pass\",\n  \"icon\": \"assets/linux/icon.png\",\n  \"asar\": false,\n  \"directories\": {\n    \"output\": \"out"
  },
  {
    "path": "electron-builder.mac.json",
    "chars": 786,
    "preview": "{\n  \"appId\": \"com.pears.pass\",\n  \"icon\": \"assets/darwin/icon.png\",\n  \"asar\": false,\n  \"directories\": {\n    \"output\": \"ou"
  },
  {
    "path": "eslint.config.js",
    "chars": 747,
    "preview": "import { eslintConfig } from '@tetherto/tether-dev-docs'\nimport globals from 'globals'\n\nexport default [\n  { ignores: ['"
  },
  {
    "path": "flatpak/appimage/.gitkeep",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "flatpak/com.pears.pass.desktop",
    "chars": 372,
    "preview": "[Desktop Entry]\nName=PearPass\nGenericName=Password Manager\nComment=A secure, decentralized and fully local password mana"
  },
  {
    "path": "flatpak/com.pears.pass.metainfo.xml",
    "chars": 4081,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!-- Copyright 2024 Tether Data S.A. de C.V. -->\n<component type=\"desktop-applica"
  },
  {
    "path": "flatpak/com.pears.pass.yaml",
    "chars": 3522,
    "preview": "app-id: com.pears.pass\nruntime: org.gnome.Platform\nruntime-version: '49'\nsdk: org.gnome.Sdk\ncommand: pearpass\n\nfinish-ar"
  },
  {
    "path": "forge.config.cjs",
    "chars": 4399,
    "preview": "const fs = require('fs')\nconst path = require('path')\n\nconst { isWindows } = require('which-runtime')\n\nconst pkg = requi"
  },
  {
    "path": "index.html",
    "chars": 542,
    "preview": "<!doctype html>\n<html>\n  <body style=\"max-width: 100%; min-width: 1080px; min-height: 900px; overflow: auto;\">\n    <div "
  },
  {
    "path": "index.js",
    "chars": 295,
    "preview": "import Runtime from 'pear-electron'\nimport Bridge from 'pear-bridge'\n\nconst bridge = new Bridge({ waypoint: '/index.html"
  },
  {
    "path": "jest.config.js",
    "chars": 1240,
    "preview": "export default {\n  testEnvironment: 'jsdom',\n  transform: {\n    '^.+\\\\.[jt]sx?$': 'babel-jest'\n  },\n  moduleNameMapper: "
  },
  {
    "path": "lingui.config.js",
    "chars": 448,
    "preview": "import { defineConfig } from '@lingui/cli'\nimport { formatter } from '@lingui/format-json'\n\nexport default defineConfig("
  },
  {
    "path": "package.json",
    "chars": 10760,
    "preview": "{\n  \"name\": \"pearpass-app-desktop\",\n  \"productName\": \"PearPass\",\n  \"version\": \"2.0.0\",\n  \"description\": \"PearPass passwo"
  },
  {
    "path": "scripts/afterPack.cjs",
    "chars": 5112,
    "preview": "#!/usr/bin/env node\n/**\n * Electron-builder afterPack hook.\n *\n * 1. Prune native `prebuilds/<platform>-<arch>` dirs tha"
  },
  {
    "path": "scripts/apply-flavor.mjs",
    "chars": 3807,
    "preview": "#!/usr/bin/env node\n/**\n * Apply build-flavor branding to the working tree. CI-only: mutates icon\n * files and config fi"
  },
  {
    "path": "scripts/build-flatpak.sh",
    "chars": 5941,
    "preview": "#!/bin/bash\nset -euo pipefail\n\nSCRIPT_DIR=\"$(cd \"$(dirname \"$0\")\" && pwd)\"\nPROJECT_DIR=\"$(cd \"$SCRIPT_DIR/..\" && pwd)\"\nF"
  },
  {
    "path": "scripts/build-snap.sh",
    "chars": 6269,
    "preview": "#!/bin/bash\nset -euo pipefail\n\nSCRIPT_DIR=\"$(cd \"$(dirname \"$0\")\" && pwd)\"\nPROJECT_DIR=\"$(cd \"$SCRIPT_DIR/..\" && pwd)\"\nS"
  },
  {
    "path": "scripts/build.worklet.mjs",
    "chars": 1441,
    "preview": "#!/usr/bin/env node\n/* eslint-disable no-underscore-dangle */\n/**\n * Bundle the vault worklet (ESM) to CommonJS for the "
  },
  {
    "path": "scripts/bundle-bridge.mjs",
    "chars": 1078,
    "preview": "#!/usr/bin/env node\n/**\n * Bundle the native messaging bridge into a single CJS file for Electron (ELECTRON_RUN_AS_NODE)"
  },
  {
    "path": "scripts/bundle-renderer.mjs",
    "chars": 4190,
    "preview": "#!/usr/bin/env node\n/**\n * Bundle the renderer (app.electron.tsx + deps) into a single file for Electron.\n * Node built-"
  },
  {
    "path": "scripts/create-linux-tarball.sh",
    "chars": 4236,
    "preview": "#!/bin/bash\n# PearPass Desktop - Create Linux tar.gz archive from AppImage\n# Creates a consistent archive structure for "
  },
  {
    "path": "scripts/notarize.cjs",
    "chars": 761,
    "preview": "#!/usr/bin/env node\n/**\n * Electron-builder afterSign hook: notarize the macOS app using the \"notary\" keychain profile.\n"
  },
  {
    "path": "scripts/patch-electron-dock-name.mjs",
    "chars": 2728,
    "preview": "#!/usr/bin/env node\n/* eslint-disable no-underscore-dangle */\n/**\n * On macOS, make the dock show \"PearPass\" when runnin"
  },
  {
    "path": "snap/snapcraft.yaml",
    "chars": 6127,
    "preview": "name: pearpass\nadopt-info: pearpass\ntitle: PearPass\nsummary: A secure, decentralized and fully local password manager\nde"
  },
  {
    "path": "src/app/App/appConfig.js",
    "chars": 80,
    "preview": "export const appConfig = {\n  headerWithLogo: ['settings', 'welcome', 'intro']\n}\n"
  },
  {
    "path": "src/app/App/hooks/useInactivity.js",
    "chars": 3405,
    "preview": "import { useEffect, useRef } from 'react'\n\nimport {\n  closeAllInstances,\n  useUserData,\n  useVaults\n} from '@tetherto/pe"
  },
  {
    "path": "src/app/App/hooks/useInactivity.test.js",
    "chars": 3351,
    "preview": "import React from 'react'\n\nimport { render, act } from '@testing-library/react'\n\nconst { useInactivity } = require('./us"
  },
  {
    "path": "src/app/App/hooks/useOnExtension.test.js",
    "chars": 1828,
    "preview": "import React from 'react'\n\nimport { render } from '@testing-library/react'\n\nimport { HANDLER_EVENTS } from '../../../con"
  },
  {
    "path": "src/app/App/hooks/useOnExtensionExit.js",
    "chars": 784,
    "preview": "import { useEffect } from 'react'\n\nimport { useVaults } from '@tetherto/pearpass-lib-vault'\n\nimport { NAVIGATION_ROUTES "
  },
  {
    "path": "src/app/App/hooks/useOnExtensionLockOut.js",
    "chars": 699,
    "preview": "import { useEffect } from 'react'\n\nimport { NAVIGATION_ROUTES } from '../../../constants/navigation'\nimport { HANDLER_EV"
  },
  {
    "path": "src/app/App/hooks/useOnExtensionLockOut.test.js",
    "chars": 1708,
    "preview": "import React from 'react'\n\nimport { render } from '@testing-library/react'\n\nimport { useOnExtensionLockOut } from './use"
  },
  {
    "path": "src/app/App/hooks/useRedirect.js",
    "chars": 1359,
    "preview": "import { useEffect, useState } from 'react'\n\nimport { useUserData } from '@tetherto/pearpass-lib-vault'\n\nimport { NAVIGA"
  },
  {
    "path": "src/app/App/hooks/useRedirect.test.js",
    "chars": 2330,
    "preview": "import { renderHook, waitFor } from '@testing-library/react'\nimport { useUserData } from '@tetherto/pearpass-lib-vault'\n"
  },
  {
    "path": "src/app/App/index.js",
    "chars": 2312,
    "preview": "import { useState, useCallback } from 'react'\n\nimport { useTheme } from '@tetherto/pearpass-lib-ui-kit'\nimport { html } "
  },
  {
    "path": "src/app/App/styles.js",
    "chars": 1171,
    "preview": "import { rawTokens } from '@tetherto/pearpass-lib-ui-kit'\nimport styled from 'styled-components'\n\nimport { isV2 } from '"
  },
  {
    "path": "src/app/Routes/index.js",
    "chars": 2788,
    "preview": "import { AUTHENTICATOR_ENABLED } from '@tetherto/pearpass-lib-constants'\nimport { OtpRefreshProvider, RECORD_TYPES } fro"
  },
  {
    "path": "src/components/AlertBox/index.tsx",
    "chars": 1335,
    "preview": "import { useRef, useEffect, useState } from 'react'\nimport { IconWrapper, Container, Message } from './styles'\nimport { "
  },
  {
    "path": "src/components/AlertBox/styles.ts",
    "chars": 1002,
    "preview": "import styled from 'styled-components'\nimport { AlertBoxType } from '.'\n\ninterface ContainerProps {\n  $isMultiLine?: boo"
  },
  {
    "path": "src/components/AppHeaderV2/AppHeaderV2.styles.ts",
    "chars": 1329,
    "preview": "import type { ThemeColors } from '@tetherto/pearpass-lib-ui-kit'\nimport { rawTokens } from '@tetherto/pearpass-lib-ui-ki"
  },
  {
    "path": "src/components/AppHeaderV2/AppHeaderV2.test.js",
    "chars": 3241,
    "preview": "import React from 'react'\n\nimport '@testing-library/jest-dom'\nimport { render, screen, fireEvent } from '@testing-librar"
  },
  {
    "path": "src/components/AppHeaderV2/AppHeaderV2.tsx",
    "chars": 2077,
    "preview": "import React, { type ReactNode } from 'react'\n\nimport { Button, useTheme, SearchField } from '@tetherto/pearpass-lib-ui-"
  },
  {
    "path": "src/components/AppHeaderV2/index.ts",
    "chars": 99,
    "preview": "export {\n  AppHeaderV2,\n  AppHeaderAddItemTrigger,\n  type AppHeaderV2Props,\n} from './AppHeaderV2'\n"
  },
  {
    "path": "src/components/BackgroundWithGradient.tsx",
    "chars": 2462,
    "preview": "import React, { useState, useEffect, ReactNode, CSSProperties } from 'react'\nimport styled from 'styled-components'\nimpo"
  },
  {
    "path": "src/components/BadgeTextItem/index.js",
    "chars": 465,
    "preview": "import { html } from 'htm/react'\n\nimport { BadgeContainer, BadgeText, BadgeCount } from './styles'\n\n/**\n * @param {{\n * "
  },
  {
    "path": "src/components/BadgeTextItem/index.test.js",
    "chars": 1074,
    "preview": "import React from 'react'\n\nimport { render, screen } from '@testing-library/react'\nimport '@testing-library/jest-dom'\n\ni"
  },
  {
    "path": "src/components/BadgeTextItem/styles.js",
    "chars": 700,
    "preview": "import styled from 'styled-components'\n\nexport const BadgeContainer = styled.div`\n  display: flex;\n  flex-direction: row"
  },
  {
    "path": "src/components/BannerBox/index.js",
    "chars": 1275,
    "preview": "import { html } from 'htm/react'\n\nimport {\n  CloseButtonWrapper,\n  Container,\n  HighlightedDescription,\n  Message,\n  Tit"
  },
  {
    "path": "src/components/BannerBox/styles.js",
    "chars": 1174,
    "preview": "import styled from 'styled-components'\n\nexport const Container = styled.div`\n  position: absolute;\n  display: flex;\n  bo"
  },
  {
    "path": "src/components/ButtonPlusCreateNew/index.js",
    "chars": 444,
    "preview": "import { colors } from '@tetherto/pearpass-lib-ui-theme-provider'\nimport { html } from 'htm/react'\n\nimport { Button } fr"
  },
  {
    "path": "src/components/ButtonPlusCreateNew/index.test.js",
    "chars": 1375,
    "preview": "import React from 'react'\n\nimport { render } from '@testing-library/react'\n\nimport { ButtonPlusCreateNew } from './index"
  },
  {
    "path": "src/components/ButtonPlusCreateNew/styles.js",
    "chars": 354,
    "preview": "import styled from 'styled-components'\n\nexport const Button = styled.button`\n  display: flex;\n  position: relative;\n  wi"
  },
  {
    "path": "src/components/CardSingleSetting/index.js",
    "chars": 644,
    "preview": "import { html } from 'htm/react'\n\nimport { Container, Description, Header, Title } from './styles'\n\n/**\n * @param {{\n * "
  },
  {
    "path": "src/components/CardSingleSetting/index.test.js",
    "chars": 1210,
    "preview": "import React from 'react'\n\nimport { render } from '@testing-library/react'\n\nimport { CardSingleSetting } from './index'\n"
  },
  {
    "path": "src/components/CardSingleSetting/styles.js",
    "chars": 827,
    "preview": "import styled from 'styled-components'\n\nexport const Container = styled.div`\n  padding: 17px 20px;\n  border-radius: 10px"
  },
  {
    "path": "src/components/CopyButton/index.tsx",
    "chars": 1458,
    "preview": "import React from 'react'\nimport { useCopyToClipboard } from '../../hooks/useCopyToClipboard.electron'\nimport { useToast"
  },
  {
    "path": "src/components/CreateCustomField/index.js",
    "chars": 2214,
    "preview": "import { useState } from 'react'\n\nimport { useLingui } from '@lingui/react'\nimport { html } from 'htm/react'\n\nimport { A"
  },
  {
    "path": "src/components/CreateCustomField/index.test.js",
    "chars": 2331,
    "preview": "import React from 'react'\n\nimport { render, fireEvent } from '@testing-library/react'\nimport { ThemeProvider } from '@te"
  },
  {
    "path": "src/components/CreateCustomField/styles.js",
    "chars": 788,
    "preview": "import styled from 'styled-components'\n\nexport const Wrapper = styled.div`\n  border-radius: 10px;\n  background: ${({ the"
  },
  {
    "path": "src/components/CreateNewCategoryPopupContent/index.js",
    "chars": 1122,
    "preview": "import { html } from 'htm/react'\n\nimport { MenuItem, MenuList } from './styles'\nimport { RECORD_COLOR_BY_TYPE } from '.."
  },
  {
    "path": "src/components/CreateNewCategoryPopupContent/index.test.js",
    "chars": 1326,
    "preview": "import React from 'react'\n\nimport { render, fireEvent } from '@testing-library/react'\nimport { ThemeProvider } from '@te"
  },
  {
    "path": "src/components/CreateNewCategoryPopupContent/styles.js",
    "chars": 1017,
    "preview": "import styled from 'styled-components'\n\nexport const MenuList = styled.div`\n  display: flex;\n  font-family: 'Inter';\n  p"
  },
  {
    "path": "src/components/DropdownSwapVault/index.test.js",
    "chars": 2190,
    "preview": "import React from 'react'\n\nimport { render, fireEvent } from '@testing-library/react'\nimport { ThemeProvider } from '@te"
  },
  {
    "path": "src/components/DropdownSwapVault/index.tsx",
    "chars": 4748,
    "preview": "import React, { useEffect, useState } from 'react'\n\nimport { html } from 'htm/react'\nimport { colors } from '@tetherto/p"
  },
  {
    "path": "src/components/DropdownSwapVault/styles.ts",
    "chars": 3136,
    "preview": "import styled from 'styled-components'\n\nimport { BASE_TRANSITION_DURATION } from '../../constants/transitions'\n\nconst RO"
  },
  {
    "path": "src/components/EditFolderPopupContent/index.js",
    "chars": 3319,
    "preview": "import { useMemo } from 'react'\n\nimport { useLingui } from '@lingui/react'\nimport { useFolders } from '@tetherto/pearpas"
  },
  {
    "path": "src/components/EditFolderPopupContent/styles.js",
    "chars": 1017,
    "preview": "import styled from 'styled-components'\n\nexport const MenuList = styled.div`\n  display: flex;\n  font-family: 'Inter';\n  p"
  },
  {
    "path": "src/components/EmptyCollectionView/index.js",
    "chars": 2683,
    "preview": "import { RECORD_TYPES } from '@tetherto/pearpass-lib-vault'\nimport { html } from 'htm/react'\n\nimport {\n  CollectionsCont"
  },
  {
    "path": "src/components/EmptyCollectionView/index.test.js",
    "chars": 3509,
    "preview": "import React from 'react'\n\nimport { render, fireEvent } from '@testing-library/react'\nimport { ThemeProvider } from '@te"
  },
  {
    "path": "src/components/EmptyCollectionView/styles.js",
    "chars": 788,
    "preview": "import styled from 'styled-components'\n\nexport const CollectionsWrapper = styled.div`\n  display: flex;\n  width: 100%;\n  "
  },
  {
    "path": "src/components/FileDropArea/index.js",
    "chars": 1277,
    "preview": "import { useRef, useState } from 'react'\n\nimport { html } from 'htm/react'\n\nimport { DropAreaWrapper, HiddenInput, Label"
  },
  {
    "path": "src/components/FileDropArea/index.test.js",
    "chars": 1984,
    "preview": "import React from 'react'\n\nimport { render, fireEvent } from '@testing-library/react'\nimport { ThemeProvider } from '@te"
  },
  {
    "path": "src/components/FileDropArea/styles.js",
    "chars": 610,
    "preview": "import styled from 'styled-components'\n\nexport const DropAreaWrapper = styled.div`\n  display: flex;\n  justify-content: c"
  },
  {
    "path": "src/components/FileUploadContent/index.js",
    "chars": 1912,
    "preview": "import { useRef, useState } from 'react'\n\nimport {\n  MAX_FILE_SIZE_MB,\n  MAX_FILE_SIZE_BYTES\n} from '@tetherto/pearpass-"
  },
  {
    "path": "src/components/FileUploadContent/styles.js",
    "chars": 354,
    "preview": "import styled from 'styled-components'\n\nexport const ContentWrapper = styled.div`\n  width: 100%;\n`\n\nexport const HiddenI"
  },
  {
    "path": "src/components/FolderDropdown/FolderDropdownV2.tsx",
    "chars": 3573,
    "preview": "import { useMemo } from 'react'\n\nimport {\n  Button,\n  ContextMenu,\n  MultiSlotInput,\n  NavbarListItem,\n  SelectField,\n  "
  },
  {
    "path": "src/components/FolderDropdown/index.js",
    "chars": 3134,
    "preview": "import React, { useEffect } from 'react'\n\nimport { useFolders } from '@tetherto/pearpass-lib-vault'\nimport { html } from"
  },
  {
    "path": "src/components/FolderDropdown/index.test.js",
    "chars": 5011,
    "preview": "import React from 'react'\n\nimport { useLingui } from '@lingui/react'\nimport { render, screen, fireEvent } from '@testing"
  },
  {
    "path": "src/components/FolderDropdown/styles.js",
    "chars": 1599,
    "preview": "import styled, { css } from 'styled-components'\n\nexport const Label = styled.div.withConfig({\n  shouldForwardProp: (prop"
  },
  {
    "path": "src/components/FormGroup/index.js",
    "chars": 941,
    "preview": "import { useState } from 'react'\n\nimport { html } from 'htm/react'\n\nimport { Collapse, TitleWrapper, Wrapper } from './s"
  },
  {
    "path": "src/components/FormGroup/index.test.js",
    "chars": 3150,
    "preview": "import React from 'react'\n\nimport { render, screen, fireEvent } from '@testing-library/react'\nimport { ThemeProvider } f"
  },
  {
    "path": "src/components/FormGroup/styles.js",
    "chars": 467,
    "preview": "import styled from 'styled-components'\n\nexport const Wrapper = styled.div`\n  width: 100%;\n  display: flex;\n  flex-direct"
  },
  {
    "path": "src/components/FormModalHeaderWrapper/index.js",
    "chars": 404,
    "preview": "import { html } from 'htm/react'\n\nimport { Buttons, FormModalHeaderWrapperComponent, Main } from './styles'\n\n/**\n * @par"
  },
  {
    "path": "src/components/FormModalHeaderWrapper/index.test.js",
    "chars": 1801,
    "preview": "import React from 'react'\n\nimport { render, screen } from '@testing-library/react'\nimport { ThemeProvider } from '@tethe"
  },
  {
    "path": "src/components/FormModalHeaderWrapper/styles.js",
    "chars": 318,
    "preview": "import styled from 'styled-components'\n\nexport const FormModalHeaderWrapperComponent = styled.div`\n  display: flex;\n  ju"
  },
  {
    "path": "src/components/FormWrapper/index.js",
    "chars": 137,
    "preview": "import styled from 'styled-components'\n\nexport const FormWrapper = styled.div`\n  display: flex;\n  flex-direction: column"
  },
  {
    "path": "src/components/FormWrapper/index.test.js",
    "chars": 1050,
    "preview": "import React from 'react'\n\nimport { render } from '@testing-library/react'\nimport { ThemeProvider } from '@tetherto/pear"
  },
  {
    "path": "src/components/ImportDataOption/index.js",
    "chars": 924,
    "preview": "import { html } from 'htm/react'\n\nimport { AcceptedTypes, Container, Title } from './styles'\nimport { UploadFilesModalCo"
  },
  {
    "path": "src/components/ImportDataOption/styles.js",
    "chars": 726,
    "preview": "import styled from 'styled-components'\n\nexport const Container = styled.div`\n  display: flex;\n  width: 150px;\n  height: "
  },
  {
    "path": "src/components/InitialPageWrapper/index.js",
    "chars": 839,
    "preview": "import { html } from 'htm/react'\n\nimport {\n  Background,\n  BottomGradient,\n  ContentWrapper,\n  LeftSpotlightWrapper,\n  L"
  },
  {
    "path": "src/components/InitialPageWrapper/index.test.js",
    "chars": 936,
    "preview": "import React from 'react'\n\nimport { render, screen } from '@testing-library/react'\nimport { ThemeProvider } from '@tethe"
  },
  {
    "path": "src/components/InitialPageWrapper/styles.js",
    "chars": 2025,
    "preview": "import styled from 'styled-components'\n\nexport const Background = styled.div`\n  position: relative;\n  background-color: "
  },
  {
    "path": "src/components/InputFieldNote/index.js",
    "chars": 843,
    "preview": "import { useLingui } from '@lingui/react'\nimport { html } from 'htm/react'\n\nimport { CommonFileIcon, InputField } from '"
  },
  {
    "path": "src/components/InputFieldNote/index.test.js",
    "chars": 2317,
    "preview": "import React from 'react'\n\nimport { render, screen, fireEvent } from '@testing-library/react'\nimport { ThemeProvider } f"
  },
  {
    "path": "src/components/InputPearpassPassword/index.js",
    "chars": 1491,
    "preview": "import { useState } from 'react'\n\nimport { html } from 'htm/react'\n\nimport {\n  AdditionalItems,\n  IconWrapper,\n  Input,\n"
  },
  {
    "path": "src/components/InputPearpassPassword/styles.js",
    "chars": 1716,
    "preview": "import styled from 'styled-components'\n\nexport const InputWrapper = styled.div`\n  display: flex;\n  align-items: center;\n"
  },
  {
    "path": "src/components/InputSearch/index.js",
    "chars": 812,
    "preview": "import { useLingui } from '@lingui/react'\nimport { html } from 'htm/react'\n\nimport { Container, IconWrapper, Input, Quan"
  },
  {
    "path": "src/components/InputSearch/index.test.js",
    "chars": 2503,
    "preview": "import React from 'react'\n\nimport { render, screen, fireEvent } from '@testing-library/react'\nimport { ThemeProvider } f"
  },
  {
    "path": "src/components/InputSearch/styles.js",
    "chars": 796,
    "preview": "import styled from 'styled-components'\n\nexport const Container = styled.div`\n  width: 100%;\n  display: flex;\n  gap: 10px"
  },
  {
    "path": "src/components/ListItem/index.js",
    "chars": 1497,
    "preview": "import { colors } from '@tetherto/pearpass-lib-ui-theme-provider'\nimport { html } from 'htm/react'\n\nimport {\n  SelectedL"
  },
  {
    "path": "src/components/ListItem/index.test.js",
    "chars": 3768,
    "preview": "import React from 'react'\n\nimport { render, fireEvent } from '@testing-library/react'\nimport { ThemeProvider } from '@te"
  },
  {
    "path": "src/components/ListItem/styles.js",
    "chars": 1496,
    "preview": "import styled from 'styled-components'\n\nexport const ListItemContainer = styled.div`\n  display: flex;\n  width: 100%;\n  p"
  },
  {
    "path": "src/components/LoadingOverlay/index.js",
    "chars": 188,
    "preview": "import styled from 'styled-components'\n\nexport const LoadingOverlay = styled.div`\n  position: fixed;\n  top: 0;\n  left: 0"
  },
  {
    "path": "src/components/LoadingOverlay/index.test.js",
    "chars": 904,
    "preview": "import React from 'react'\n\nimport { render } from '@testing-library/react'\nimport { ThemeProvider } from '@tetherto/pear"
  },
  {
    "path": "src/components/MenuDropdown/MenuDropdownItem/index.js",
    "chars": 525,
    "preview": "import { html } from 'htm/react'\n\nimport { FolderIcon } from '../../../lib-react-components'\nimport { DropDownItem } fro"
  },
  {
    "path": "src/components/MenuDropdown/MenuDropdownItem/index.test.js",
    "chars": 2230,
    "preview": "import React from 'react'\n\nimport { render, screen } from '@testing-library/react'\nimport { ThemeProvider } from '@tethe"
  },
  {
    "path": "src/components/MenuDropdown/MenuDropdownLabel/index.js",
    "chars": 1041,
    "preview": "import { useLingui } from '@lingui/react'\nimport { html } from 'htm/react'\n\nimport { ArrowDownIcon, ArrowUpIcon } from '"
  },
  {
    "path": "src/components/MenuDropdown/MenuDropdownLabel/index.test.js",
    "chars": 2622,
    "preview": "import React from 'react'\n\nimport { render, screen } from '@testing-library/react'\nimport { ThemeProvider } from '@tethe"
  },
  {
    "path": "src/components/MenuDropdown/index.js",
    "chars": 2083,
    "preview": "import React, { useState } from 'react'\n\nimport { html } from 'htm/react'\n\nimport { MenuDropdownItem } from './MenuDropd"
  },
  {
    "path": "src/components/MenuDropdown/index.test.js",
    "chars": 3567,
    "preview": "import React from 'react'\n\nimport { render, screen, fireEvent } from '@testing-library/react'\nimport { ThemeProvider } f"
  },
  {
    "path": "src/components/MenuDropdown/styles.js",
    "chars": 1642,
    "preview": "import styled, { css } from 'styled-components'\n\nexport const Label = styled.div.withConfig({\n  shouldForwardProp: (prop"
  },
  {
    "path": "src/components/NoticeContainer/index.js",
    "chars": 259,
    "preview": "import { html } from 'htm/react'\n\nimport { Container } from './styles'\nimport { YellowErrorIcon } from '../../lib-react-"
  },
  {
    "path": "src/components/NoticeContainer/styles.js",
    "chars": 644,
    "preview": "import styled from 'styled-components'\n\nexport const Container = styled.div`\n  display: inline-flex;\n  justify-content: "
  },
  {
    "path": "src/components/OnboardingShell/index.tsx",
    "chars": 1003,
    "preview": "import React from 'react'\nimport { useTheme } from '@tetherto/pearpass-lib-ui-kit'\nimport { BackgroundWithGradient } fro"
  },
  {
    "path": "src/components/OnboardingShell/styles.ts",
    "chars": 1823,
    "preview": "import styled from 'styled-components'\nimport { rawTokens } from '@tetherto/pearpass-lib-ui-kit'\n\ninterface SolidBackgro"
  },
  {
    "path": "src/components/OtpCodeField/index.test.js",
    "chars": 4159,
    "preview": "import React from 'react'\n\nimport { render, screen, fireEvent } from '@testing-library/react'\nimport '@testing-library/j"
  },
  {
    "path": "src/components/OtpCodeField/index.ts",
    "chars": 1994,
    "preview": "import { useLingui } from '@lingui/react'\nimport { html } from 'htm/react'\nimport { useOtp, formatOtpCode, OTP_TYPE } fr"
  },
  {
    "path": "src/components/OtpCodeField/utils.ts",
    "chars": 188,
    "preview": "import { colors } from '@tetherto/pearpass-lib-ui-theme-provider'\n\nexport const getTimerColor = (expiring: boolean): str"
  },
  {
    "path": "src/components/OtpCodeFieldV2/index.test.tsx",
    "chars": 5386,
    "preview": "import React from 'react'\n\nimport '@testing-library/jest-dom'\nimport { fireEvent, render, screen } from '@testing-librar"
  }
]

// ... and 717 more files (download for full content)

About this extraction

This page contains the full source code of the tetherto/pearpass-app-desktop GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 917 files (4.1 MB), approximately 1.1M tokens, and a symbol index with 790 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!