From 8ac2ce5273edce9d396c7a15152492dbd186625c Mon Sep 17 00:00:00 2001 From: Michael Freno Date: Mon, 25 May 2026 23:08:11 -0400 Subject: [PATCH] reduced nesting --- android/Kordant/.gitignore | 15 - android/Kordant/.idea/.gitignore | 3 - android/{Kordant => }/app/.gitignore | 0 android/{Kordant => }/app/build.gradle.kts | 0 android/{Kordant => }/app/lint-baseline.xml | 0 android/{Kordant => }/app/proguard-rules.pro | 0 .../com/shieldai/android/ComponentTests.kt | 0 .../android/ExampleInstrumentedTest.kt | 0 .../app/src/main/AndroidManifest.xml | 0 .../java/com/kordant}/android/MainActivity.kt | 0 .../java/com/kordant}/android/ShieldAIApp.kt | 0 .../android/data/local/CacheManager.kt | 0 .../com/kordant}/android/data/model/Alert.kt | 0 .../android/data/model/BrokerListing.kt | 0 .../kordant}/android/data/model/Exposure.kt | 0 .../kordant}/android/data/model/Property.kt | 0 .../android/data/model/RemovalRequest.kt | 0 .../kordant}/android/data/model/SpamRule.kt | 0 .../android/data/model/Subscription.kt | 0 .../com/kordant}/android/data/model/User.kt | 0 .../android/data/model/VoiceAnalysis.kt | 0 .../android/data/model/VoiceEnrollment.kt | 0 .../android/data/model/WatchlistItem.kt | 0 .../android/data/remote/AuthInterceptor.kt | 0 .../android/data/remote/ErrorHandler.kt | 0 .../android/data/remote/TRPCApiService.kt | 0 .../android/data/remote/TRPCResponse.kt | 0 .../data/repository/AlertRepository.kt | 0 .../android/data/repository/AuthRepository.kt | 0 .../data/repository/DarkWatchRepository.kt | 0 .../data/repository/HomeTitleRepository.kt | 0 .../repository/RemoveBrokersRepository.kt | 0 .../data/repository/SpamShieldRepository.kt | 0 .../data/repository/SubscriptionRepository.kt | 0 .../android/data/repository/UserRepository.kt | 0 .../data/repository/VoicePrintRepository.kt | 0 .../android/data/sync/OfflineWorker.kt | 0 .../android/data/sync/PendingRequestQueue.kt | 0 .../kordant}/android/data/sync/SyncManager.kt | 0 .../com/kordant}/android/di/DatabaseModule.kt | 0 .../com/kordant}/android/di/NetworkModule.kt | 0 .../kordant}/android/di/RepositoryModule.kt | 0 .../android/navigation/AppNavigation.kt | 0 .../android/navigation/BottomNavBar.kt | 0 .../kordant}/android/navigation/NavGraph.kt | 0 .../com/kordant}/android/navigation/Screen.kt | 0 .../ui/components/ComponentShowcase.kt | 0 .../android/ui/components/ShieldAvatar.kt | 0 .../android/ui/components/ShieldBadge.kt | 0 .../android/ui/components/ShieldButton.kt | 0 .../android/ui/components/ShieldCard.kt | 0 .../android/ui/components/ShieldEmptyState.kt | 0 .../android/ui/components/ShieldModal.kt | 0 .../ui/components/ShieldProgressBar.kt | 0 .../android/ui/components/ShieldSkeleton.kt | 0 .../android/ui/components/ShieldTextField.kt | 0 .../android/ui/components/ShieldToast.kt | 0 .../android/ui/components/ThreatGauge.kt | 0 .../android/ui/screens/auth/AuthScreen.kt | 0 .../ui/screens/auth/BiometricAuthScreen.kt | 0 .../ui/screens/auth/ForgotPasswordScreen.kt | 0 .../android/ui/screens/auth/LoginScreen.kt | 0 .../ui/screens/auth/ResetPasswordScreen.kt | 0 .../android/ui/screens/auth/SignupScreen.kt | 0 .../ui/screens/dashboard/AlertDetailScreen.kt | 0 .../ui/screens/dashboard/DashboardScreen.kt | 0 .../ui/screens/onboarding/CompleteStep.kt | 0 .../ui/screens/onboarding/FamilyInviteStep.kt | 0 .../ui/screens/onboarding/OnboardingScreen.kt | 0 .../screens/onboarding/PlanSelectionStep.kt | 0 .../screens/onboarding/WatchlistSetupStep.kt | 0 .../ui/screens/services/DarkWatchScreen.kt | 0 .../ui/screens/services/HomeTitleScreen.kt | 0 .../screens/services/RemoveBrokersScreen.kt | 0 .../ui/screens/services/SpamShieldScreen.kt | 0 .../ui/screens/services/VoicePrintScreen.kt | 0 .../ui/screens/settings/SettingsScreen.kt | 0 .../com/kordant}/android/ui/theme/Color.kt | 0 .../com/kordant}/android/ui/theme/Shape.kt | 0 .../com/kordant}/android/ui/theme/Theme.kt | 0 .../com/kordant}/android/ui/theme/Type.kt | 0 .../kordant}/android/util/PasswordStrength.kt | 0 .../android/viewmodel/AlertDetailViewModel.kt | 0 .../android/viewmodel/AuthViewModel.kt | 0 .../android/viewmodel/DarkWatchViewModel.kt | 0 .../android/viewmodel/DashboardViewModel.kt | 0 .../android/viewmodel/HomeTitleViewModel.kt | 0 .../viewmodel/RemoveBrokersViewModel.kt | 0 .../android/viewmodel/SettingsViewModel.kt | 0 .../android/viewmodel/SpamShieldViewModel.kt | 0 .../android/viewmodel/VoicePrintViewModel.kt | 0 .../src/main/res/drawable/ic_account_box.xml | 0 .../app/src/main/res/drawable/ic_alerts.xml | 0 .../src/main/res/drawable/ic_dashboard.xml | 0 .../app/src/main/res/drawable/ic_home.xml | 0 .../res/drawable/ic_launcher_background.xml | 0 .../res/drawable/ic_launcher_foreground.xml | 0 .../app/src/main/res/drawable/ic_services.xml | 0 .../app/src/main/res/drawable/ic_settings.xml | 0 .../main/res/mipmap-anydpi/ic_launcher.xml | 0 .../res/mipmap-anydpi/ic_launcher_round.xml | 0 .../src/main/res/mipmap-hdpi/ic_launcher.webp | Bin .../res/mipmap-hdpi/ic_launcher_round.webp | Bin .../src/main/res/mipmap-mdpi/ic_launcher.webp | Bin .../res/mipmap-mdpi/ic_launcher_round.webp | Bin .../main/res/mipmap-xhdpi/ic_launcher.webp | Bin .../res/mipmap-xhdpi/ic_launcher_round.webp | Bin .../main/res/mipmap-xxhdpi/ic_launcher.webp | Bin .../res/mipmap-xxhdpi/ic_launcher_round.webp | Bin .../main/res/mipmap-xxxhdpi/ic_launcher.webp | Bin .../res/mipmap-xxxhdpi/ic_launcher_round.webp | Bin .../app/src/main/res/values/colors.xml | 0 .../app/src/main/res/values/strings.xml | 0 .../app/src/main/res/values/themes.xml | 0 .../app/src/main/res/xml/backup_rules.xml | 0 .../main/res/xml/data_extraction_rules.xml | 0 .../com/shieldai/android/ExampleUnitTest.kt | 0 .../android/data/local/CacheManagerTest.kt | 0 .../android/data/remote/ErrorHandlerTest.kt | 0 .../android/data/remote/TRPCResponseTest.kt | 0 .../android/data/sync/SyncManagerTest.kt | 0 .../android/util/PasswordStrengthTest.kt | 0 .../android/viewmodel/AuthViewModelTest.kt | 0 .../viewmodel/ServiceViewModelsTest.kt | 0 android/{Kordant => }/build.gradle.kts | 0 android/{Kordant => }/gradle.properties | 0 .../gradle/gradle-daemon-jvm.properties | 0 .../{Kordant => }/gradle/libs.versions.toml | 0 .../gradle/wrapper/gradle-wrapper.jar | Bin .../gradle/wrapper/gradle-wrapper.properties | 0 android/{Kordant => }/gradlew | 0 android/{Kordant => }/gradlew.bat | 0 android/local.properties | 10 + android/{Kordant => }/settings.gradle.kts | 0 bun.lock | 2 +- iOS/Kordant.xcodeproj/project.pbxproj | 594 +++++ .../contents.xcworkspacedata | 7 + .../UserInterfaceState.xcuserstate | Bin 0 -> 11847 bytes .../xcschemes/xcschememanagement.plist | 14 + iOS/KordantTests/ShieldAITests.swift | 1914 +++++++++++++++++ iOS/KordantUITests/ShieldAIUITests.swift | 43 + .../ShieldAIUITestsLaunchTests.swift | 35 + .../01-update-monorepo-foundation.md | 37 - .../02-update-database-connection-strings.md | 39 - ...03-update-web-package-scope-and-imports.md | 39 - .../04-update-web-ui-brand-text.md | 46 - ...update-web-email-notification-templates.md | 46 - ...6-update-web-storage-keys-and-constants.md | 37 - ...7-update-web-seed-data-and-blog-content.md | 32 - .../08-update-browser-extension-branding.md | 47 - .../09-update-ios-app-branding.md | 47 - .../10-update-android-app-branding.md | 54 - .../11-update-ci-cd-and-infrastructure.md | 39 - .../12-update-assets-and-ad-creatives.md | 40 - .../13-update-documentation-and-plan-files.md | 39 - .../14-verify-rebrand-completeness.md | 63 - tasks/rebrand-to-kordant/README.md | 42 - 157 files changed, 2618 insertions(+), 666 deletions(-) delete mode 100644 android/Kordant/.gitignore delete mode 100644 android/Kordant/.idea/.gitignore rename android/{Kordant => }/app/.gitignore (100%) rename android/{Kordant => }/app/build.gradle.kts (100%) rename android/{Kordant => }/app/lint-baseline.xml (100%) rename android/{Kordant => }/app/proguard-rules.pro (100%) rename android/{Kordant => }/app/src/androidTest/java/com/shieldai/android/ComponentTests.kt (100%) rename android/{Kordant => }/app/src/androidTest/java/com/shieldai/android/ExampleInstrumentedTest.kt (100%) rename android/{Kordant => }/app/src/main/AndroidManifest.xml (100%) rename android/{Kordant/app/src/main/java/com/shieldai => app/src/main/java/com/kordant}/android/MainActivity.kt (100%) rename android/{Kordant/app/src/main/java/com/shieldai => app/src/main/java/com/kordant}/android/ShieldAIApp.kt (100%) rename android/{Kordant/app/src/main/java/com/shieldai => app/src/main/java/com/kordant}/android/data/local/CacheManager.kt (100%) rename android/{Kordant/app/src/main/java/com/shieldai => app/src/main/java/com/kordant}/android/data/model/Alert.kt (100%) rename android/{Kordant/app/src/main/java/com/shieldai => app/src/main/java/com/kordant}/android/data/model/BrokerListing.kt (100%) rename android/{Kordant/app/src/main/java/com/shieldai => app/src/main/java/com/kordant}/android/data/model/Exposure.kt (100%) rename android/{Kordant/app/src/main/java/com/shieldai => app/src/main/java/com/kordant}/android/data/model/Property.kt (100%) rename android/{Kordant/app/src/main/java/com/shieldai => app/src/main/java/com/kordant}/android/data/model/RemovalRequest.kt (100%) rename android/{Kordant/app/src/main/java/com/shieldai => app/src/main/java/com/kordant}/android/data/model/SpamRule.kt (100%) rename android/{Kordant/app/src/main/java/com/shieldai => app/src/main/java/com/kordant}/android/data/model/Subscription.kt (100%) rename android/{Kordant/app/src/main/java/com/shieldai => app/src/main/java/com/kordant}/android/data/model/User.kt (100%) rename android/{Kordant/app/src/main/java/com/shieldai => app/src/main/java/com/kordant}/android/data/model/VoiceAnalysis.kt (100%) rename android/{Kordant/app/src/main/java/com/shieldai => app/src/main/java/com/kordant}/android/data/model/VoiceEnrollment.kt (100%) rename android/{Kordant/app/src/main/java/com/shieldai => app/src/main/java/com/kordant}/android/data/model/WatchlistItem.kt (100%) rename android/{Kordant/app/src/main/java/com/shieldai => app/src/main/java/com/kordant}/android/data/remote/AuthInterceptor.kt (100%) rename android/{Kordant/app/src/main/java/com/shieldai => app/src/main/java/com/kordant}/android/data/remote/ErrorHandler.kt (100%) rename android/{Kordant/app/src/main/java/com/shieldai => app/src/main/java/com/kordant}/android/data/remote/TRPCApiService.kt (100%) rename android/{Kordant/app/src/main/java/com/shieldai => app/src/main/java/com/kordant}/android/data/remote/TRPCResponse.kt (100%) rename android/{Kordant/app/src/main/java/com/shieldai => app/src/main/java/com/kordant}/android/data/repository/AlertRepository.kt (100%) rename android/{Kordant/app/src/main/java/com/shieldai => app/src/main/java/com/kordant}/android/data/repository/AuthRepository.kt (100%) rename android/{Kordant/app/src/main/java/com/shieldai => app/src/main/java/com/kordant}/android/data/repository/DarkWatchRepository.kt (100%) rename android/{Kordant/app/src/main/java/com/shieldai => app/src/main/java/com/kordant}/android/data/repository/HomeTitleRepository.kt (100%) rename android/{Kordant/app/src/main/java/com/shieldai => app/src/main/java/com/kordant}/android/data/repository/RemoveBrokersRepository.kt (100%) rename android/{Kordant/app/src/main/java/com/shieldai => app/src/main/java/com/kordant}/android/data/repository/SpamShieldRepository.kt (100%) rename android/{Kordant/app/src/main/java/com/shieldai => app/src/main/java/com/kordant}/android/data/repository/SubscriptionRepository.kt (100%) rename android/{Kordant/app/src/main/java/com/shieldai => app/src/main/java/com/kordant}/android/data/repository/UserRepository.kt (100%) rename android/{Kordant/app/src/main/java/com/shieldai => app/src/main/java/com/kordant}/android/data/repository/VoicePrintRepository.kt (100%) rename android/{Kordant/app/src/main/java/com/shieldai => app/src/main/java/com/kordant}/android/data/sync/OfflineWorker.kt (100%) rename android/{Kordant/app/src/main/java/com/shieldai => app/src/main/java/com/kordant}/android/data/sync/PendingRequestQueue.kt (100%) rename android/{Kordant/app/src/main/java/com/shieldai => app/src/main/java/com/kordant}/android/data/sync/SyncManager.kt (100%) rename android/{Kordant/app/src/main/java/com/shieldai => app/src/main/java/com/kordant}/android/di/DatabaseModule.kt (100%) rename android/{Kordant/app/src/main/java/com/shieldai => app/src/main/java/com/kordant}/android/di/NetworkModule.kt (100%) rename android/{Kordant/app/src/main/java/com/shieldai => app/src/main/java/com/kordant}/android/di/RepositoryModule.kt (100%) rename android/{Kordant/app/src/main/java/com/shieldai => app/src/main/java/com/kordant}/android/navigation/AppNavigation.kt (100%) rename android/{Kordant/app/src/main/java/com/shieldai => app/src/main/java/com/kordant}/android/navigation/BottomNavBar.kt (100%) rename android/{Kordant/app/src/main/java/com/shieldai => app/src/main/java/com/kordant}/android/navigation/NavGraph.kt (100%) rename android/{Kordant/app/src/main/java/com/shieldai => app/src/main/java/com/kordant}/android/navigation/Screen.kt (100%) rename android/{Kordant/app/src/main/java/com/shieldai => app/src/main/java/com/kordant}/android/ui/components/ComponentShowcase.kt (100%) rename android/{Kordant/app/src/main/java/com/shieldai => app/src/main/java/com/kordant}/android/ui/components/ShieldAvatar.kt (100%) rename android/{Kordant/app/src/main/java/com/shieldai => app/src/main/java/com/kordant}/android/ui/components/ShieldBadge.kt (100%) rename android/{Kordant/app/src/main/java/com/shieldai => app/src/main/java/com/kordant}/android/ui/components/ShieldButton.kt (100%) rename android/{Kordant/app/src/main/java/com/shieldai => app/src/main/java/com/kordant}/android/ui/components/ShieldCard.kt (100%) rename android/{Kordant/app/src/main/java/com/shieldai => app/src/main/java/com/kordant}/android/ui/components/ShieldEmptyState.kt (100%) rename android/{Kordant/app/src/main/java/com/shieldai => app/src/main/java/com/kordant}/android/ui/components/ShieldModal.kt (100%) rename android/{Kordant/app/src/main/java/com/shieldai => app/src/main/java/com/kordant}/android/ui/components/ShieldProgressBar.kt (100%) rename android/{Kordant/app/src/main/java/com/shieldai => app/src/main/java/com/kordant}/android/ui/components/ShieldSkeleton.kt (100%) rename android/{Kordant/app/src/main/java/com/shieldai => app/src/main/java/com/kordant}/android/ui/components/ShieldTextField.kt (100%) rename android/{Kordant/app/src/main/java/com/shieldai => app/src/main/java/com/kordant}/android/ui/components/ShieldToast.kt (100%) rename android/{Kordant/app/src/main/java/com/shieldai => app/src/main/java/com/kordant}/android/ui/components/ThreatGauge.kt (100%) rename android/{Kordant/app/src/main/java/com/shieldai => app/src/main/java/com/kordant}/android/ui/screens/auth/AuthScreen.kt (100%) rename android/{Kordant/app/src/main/java/com/shieldai => app/src/main/java/com/kordant}/android/ui/screens/auth/BiometricAuthScreen.kt (100%) rename android/{Kordant/app/src/main/java/com/shieldai => app/src/main/java/com/kordant}/android/ui/screens/auth/ForgotPasswordScreen.kt (100%) rename android/{Kordant/app/src/main/java/com/shieldai => app/src/main/java/com/kordant}/android/ui/screens/auth/LoginScreen.kt (100%) rename android/{Kordant/app/src/main/java/com/shieldai => app/src/main/java/com/kordant}/android/ui/screens/auth/ResetPasswordScreen.kt (100%) rename android/{Kordant/app/src/main/java/com/shieldai => app/src/main/java/com/kordant}/android/ui/screens/auth/SignupScreen.kt (100%) rename android/{Kordant/app/src/main/java/com/shieldai => app/src/main/java/com/kordant}/android/ui/screens/dashboard/AlertDetailScreen.kt (100%) rename android/{Kordant/app/src/main/java/com/shieldai => app/src/main/java/com/kordant}/android/ui/screens/dashboard/DashboardScreen.kt (100%) rename android/{Kordant/app/src/main/java/com/shieldai => app/src/main/java/com/kordant}/android/ui/screens/onboarding/CompleteStep.kt (100%) rename android/{Kordant/app/src/main/java/com/shieldai => app/src/main/java/com/kordant}/android/ui/screens/onboarding/FamilyInviteStep.kt (100%) rename android/{Kordant/app/src/main/java/com/shieldai => app/src/main/java/com/kordant}/android/ui/screens/onboarding/OnboardingScreen.kt (100%) rename android/{Kordant/app/src/main/java/com/shieldai => app/src/main/java/com/kordant}/android/ui/screens/onboarding/PlanSelectionStep.kt (100%) rename android/{Kordant/app/src/main/java/com/shieldai => app/src/main/java/com/kordant}/android/ui/screens/onboarding/WatchlistSetupStep.kt (100%) rename android/{Kordant/app/src/main/java/com/shieldai => app/src/main/java/com/kordant}/android/ui/screens/services/DarkWatchScreen.kt (100%) rename android/{Kordant/app/src/main/java/com/shieldai => app/src/main/java/com/kordant}/android/ui/screens/services/HomeTitleScreen.kt (100%) rename android/{Kordant/app/src/main/java/com/shieldai => app/src/main/java/com/kordant}/android/ui/screens/services/RemoveBrokersScreen.kt (100%) rename android/{Kordant/app/src/main/java/com/shieldai => app/src/main/java/com/kordant}/android/ui/screens/services/SpamShieldScreen.kt (100%) rename android/{Kordant/app/src/main/java/com/shieldai => app/src/main/java/com/kordant}/android/ui/screens/services/VoicePrintScreen.kt (100%) rename android/{Kordant/app/src/main/java/com/shieldai => app/src/main/java/com/kordant}/android/ui/screens/settings/SettingsScreen.kt (100%) rename android/{Kordant/app/src/main/java/com/shieldai => app/src/main/java/com/kordant}/android/ui/theme/Color.kt (100%) rename android/{Kordant/app/src/main/java/com/shieldai => app/src/main/java/com/kordant}/android/ui/theme/Shape.kt (100%) rename android/{Kordant/app/src/main/java/com/shieldai => app/src/main/java/com/kordant}/android/ui/theme/Theme.kt (100%) rename android/{Kordant/app/src/main/java/com/shieldai => app/src/main/java/com/kordant}/android/ui/theme/Type.kt (100%) rename android/{Kordant/app/src/main/java/com/shieldai => app/src/main/java/com/kordant}/android/util/PasswordStrength.kt (100%) rename android/{Kordant/app/src/main/java/com/shieldai => app/src/main/java/com/kordant}/android/viewmodel/AlertDetailViewModel.kt (100%) rename android/{Kordant/app/src/main/java/com/shieldai => app/src/main/java/com/kordant}/android/viewmodel/AuthViewModel.kt (100%) rename android/{Kordant/app/src/main/java/com/shieldai => app/src/main/java/com/kordant}/android/viewmodel/DarkWatchViewModel.kt (100%) rename android/{Kordant/app/src/main/java/com/shieldai => app/src/main/java/com/kordant}/android/viewmodel/DashboardViewModel.kt (100%) rename android/{Kordant/app/src/main/java/com/shieldai => app/src/main/java/com/kordant}/android/viewmodel/HomeTitleViewModel.kt (100%) rename android/{Kordant/app/src/main/java/com/shieldai => app/src/main/java/com/kordant}/android/viewmodel/RemoveBrokersViewModel.kt (100%) rename android/{Kordant/app/src/main/java/com/shieldai => app/src/main/java/com/kordant}/android/viewmodel/SettingsViewModel.kt (100%) rename android/{Kordant/app/src/main/java/com/shieldai => app/src/main/java/com/kordant}/android/viewmodel/SpamShieldViewModel.kt (100%) rename android/{Kordant/app/src/main/java/com/shieldai => app/src/main/java/com/kordant}/android/viewmodel/VoicePrintViewModel.kt (100%) rename android/{Kordant => }/app/src/main/res/drawable/ic_account_box.xml (100%) rename android/{Kordant => }/app/src/main/res/drawable/ic_alerts.xml (100%) rename android/{Kordant => }/app/src/main/res/drawable/ic_dashboard.xml (100%) rename android/{Kordant => }/app/src/main/res/drawable/ic_home.xml (100%) rename android/{Kordant => }/app/src/main/res/drawable/ic_launcher_background.xml (100%) rename android/{Kordant => }/app/src/main/res/drawable/ic_launcher_foreground.xml (100%) rename android/{Kordant => }/app/src/main/res/drawable/ic_services.xml (100%) rename android/{Kordant => }/app/src/main/res/drawable/ic_settings.xml (100%) rename android/{Kordant => }/app/src/main/res/mipmap-anydpi/ic_launcher.xml (100%) rename android/{Kordant => }/app/src/main/res/mipmap-anydpi/ic_launcher_round.xml (100%) rename android/{Kordant => }/app/src/main/res/mipmap-hdpi/ic_launcher.webp (100%) rename android/{Kordant => }/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp (100%) rename android/{Kordant => }/app/src/main/res/mipmap-mdpi/ic_launcher.webp (100%) rename android/{Kordant => }/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp (100%) rename android/{Kordant => }/app/src/main/res/mipmap-xhdpi/ic_launcher.webp (100%) rename android/{Kordant => }/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp (100%) rename android/{Kordant => }/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp (100%) rename android/{Kordant => }/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp (100%) rename android/{Kordant => }/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp (100%) rename android/{Kordant => }/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp (100%) rename android/{Kordant => }/app/src/main/res/values/colors.xml (100%) rename android/{Kordant => }/app/src/main/res/values/strings.xml (100%) rename android/{Kordant => }/app/src/main/res/values/themes.xml (100%) rename android/{Kordant => }/app/src/main/res/xml/backup_rules.xml (100%) rename android/{Kordant => }/app/src/main/res/xml/data_extraction_rules.xml (100%) rename android/{Kordant => }/app/src/test/java/com/shieldai/android/ExampleUnitTest.kt (100%) rename android/{Kordant => }/app/src/test/java/com/shieldai/android/data/local/CacheManagerTest.kt (100%) rename android/{Kordant => }/app/src/test/java/com/shieldai/android/data/remote/ErrorHandlerTest.kt (100%) rename android/{Kordant => }/app/src/test/java/com/shieldai/android/data/remote/TRPCResponseTest.kt (100%) rename android/{Kordant => }/app/src/test/java/com/shieldai/android/data/sync/SyncManagerTest.kt (100%) rename android/{Kordant => }/app/src/test/java/com/shieldai/android/util/PasswordStrengthTest.kt (100%) rename android/{Kordant => }/app/src/test/java/com/shieldai/android/viewmodel/AuthViewModelTest.kt (100%) rename android/{Kordant => }/app/src/test/java/com/shieldai/android/viewmodel/ServiceViewModelsTest.kt (100%) rename android/{Kordant => }/build.gradle.kts (100%) rename android/{Kordant => }/gradle.properties (100%) rename android/{Kordant => }/gradle/gradle-daemon-jvm.properties (100%) rename android/{Kordant => }/gradle/libs.versions.toml (100%) rename android/{Kordant => }/gradle/wrapper/gradle-wrapper.jar (100%) rename android/{Kordant => }/gradle/wrapper/gradle-wrapper.properties (100%) rename android/{Kordant => }/gradlew (100%) rename android/{Kordant => }/gradlew.bat (100%) create mode 100644 android/local.properties rename android/{Kordant => }/settings.gradle.kts (100%) create mode 100644 iOS/Kordant.xcodeproj/project.pbxproj create mode 100644 iOS/Kordant.xcodeproj/project.xcworkspace/contents.xcworkspacedata create mode 100644 iOS/Kordant.xcodeproj/project.xcworkspace/xcuserdata/mike.xcuserdatad/UserInterfaceState.xcuserstate create mode 100644 iOS/Kordant.xcodeproj/xcuserdata/mike.xcuserdatad/xcschemes/xcschememanagement.plist create mode 100644 iOS/KordantTests/ShieldAITests.swift create mode 100644 iOS/KordantUITests/ShieldAIUITests.swift create mode 100644 iOS/KordantUITests/ShieldAIUITestsLaunchTests.swift delete mode 100644 tasks/rebrand-to-kordant/01-update-monorepo-foundation.md delete mode 100644 tasks/rebrand-to-kordant/02-update-database-connection-strings.md delete mode 100644 tasks/rebrand-to-kordant/03-update-web-package-scope-and-imports.md delete mode 100644 tasks/rebrand-to-kordant/04-update-web-ui-brand-text.md delete mode 100644 tasks/rebrand-to-kordant/05-update-web-email-notification-templates.md delete mode 100644 tasks/rebrand-to-kordant/06-update-web-storage-keys-and-constants.md delete mode 100644 tasks/rebrand-to-kordant/07-update-web-seed-data-and-blog-content.md delete mode 100644 tasks/rebrand-to-kordant/08-update-browser-extension-branding.md delete mode 100644 tasks/rebrand-to-kordant/09-update-ios-app-branding.md delete mode 100644 tasks/rebrand-to-kordant/10-update-android-app-branding.md delete mode 100644 tasks/rebrand-to-kordant/11-update-ci-cd-and-infrastructure.md delete mode 100644 tasks/rebrand-to-kordant/12-update-assets-and-ad-creatives.md delete mode 100644 tasks/rebrand-to-kordant/13-update-documentation-and-plan-files.md delete mode 100644 tasks/rebrand-to-kordant/14-verify-rebrand-completeness.md delete mode 100644 tasks/rebrand-to-kordant/README.md diff --git a/android/Kordant/.gitignore b/android/Kordant/.gitignore deleted file mode 100644 index aa724b7..0000000 --- a/android/Kordant/.gitignore +++ /dev/null @@ -1,15 +0,0 @@ -*.iml -.gradle -/local.properties -/.idea/caches -/.idea/libraries -/.idea/modules.xml -/.idea/workspace.xml -/.idea/navEditor.xml -/.idea/assetWizardSettings.xml -.DS_Store -/build -/captures -.externalNativeBuild -.cxx -local.properties diff --git a/android/Kordant/.idea/.gitignore b/android/Kordant/.idea/.gitignore deleted file mode 100644 index 26d3352..0000000 --- a/android/Kordant/.idea/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -# Default ignored files -/shelf/ -/workspace.xml diff --git a/android/Kordant/app/.gitignore b/android/app/.gitignore similarity index 100% rename from android/Kordant/app/.gitignore rename to android/app/.gitignore diff --git a/android/Kordant/app/build.gradle.kts b/android/app/build.gradle.kts similarity index 100% rename from android/Kordant/app/build.gradle.kts rename to android/app/build.gradle.kts diff --git a/android/Kordant/app/lint-baseline.xml b/android/app/lint-baseline.xml similarity index 100% rename from android/Kordant/app/lint-baseline.xml rename to android/app/lint-baseline.xml diff --git a/android/Kordant/app/proguard-rules.pro b/android/app/proguard-rules.pro similarity index 100% rename from android/Kordant/app/proguard-rules.pro rename to android/app/proguard-rules.pro diff --git a/android/Kordant/app/src/androidTest/java/com/shieldai/android/ComponentTests.kt b/android/app/src/androidTest/java/com/shieldai/android/ComponentTests.kt similarity index 100% rename from android/Kordant/app/src/androidTest/java/com/shieldai/android/ComponentTests.kt rename to android/app/src/androidTest/java/com/shieldai/android/ComponentTests.kt diff --git a/android/Kordant/app/src/androidTest/java/com/shieldai/android/ExampleInstrumentedTest.kt b/android/app/src/androidTest/java/com/shieldai/android/ExampleInstrumentedTest.kt similarity index 100% rename from android/Kordant/app/src/androidTest/java/com/shieldai/android/ExampleInstrumentedTest.kt rename to android/app/src/androidTest/java/com/shieldai/android/ExampleInstrumentedTest.kt diff --git a/android/Kordant/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml similarity index 100% rename from android/Kordant/app/src/main/AndroidManifest.xml rename to android/app/src/main/AndroidManifest.xml diff --git a/android/Kordant/app/src/main/java/com/shieldai/android/MainActivity.kt b/android/app/src/main/java/com/kordant/android/MainActivity.kt similarity index 100% rename from android/Kordant/app/src/main/java/com/shieldai/android/MainActivity.kt rename to android/app/src/main/java/com/kordant/android/MainActivity.kt diff --git a/android/Kordant/app/src/main/java/com/shieldai/android/ShieldAIApp.kt b/android/app/src/main/java/com/kordant/android/ShieldAIApp.kt similarity index 100% rename from android/Kordant/app/src/main/java/com/shieldai/android/ShieldAIApp.kt rename to android/app/src/main/java/com/kordant/android/ShieldAIApp.kt diff --git a/android/Kordant/app/src/main/java/com/shieldai/android/data/local/CacheManager.kt b/android/app/src/main/java/com/kordant/android/data/local/CacheManager.kt similarity index 100% rename from android/Kordant/app/src/main/java/com/shieldai/android/data/local/CacheManager.kt rename to android/app/src/main/java/com/kordant/android/data/local/CacheManager.kt diff --git a/android/Kordant/app/src/main/java/com/shieldai/android/data/model/Alert.kt b/android/app/src/main/java/com/kordant/android/data/model/Alert.kt similarity index 100% rename from android/Kordant/app/src/main/java/com/shieldai/android/data/model/Alert.kt rename to android/app/src/main/java/com/kordant/android/data/model/Alert.kt diff --git a/android/Kordant/app/src/main/java/com/shieldai/android/data/model/BrokerListing.kt b/android/app/src/main/java/com/kordant/android/data/model/BrokerListing.kt similarity index 100% rename from android/Kordant/app/src/main/java/com/shieldai/android/data/model/BrokerListing.kt rename to android/app/src/main/java/com/kordant/android/data/model/BrokerListing.kt diff --git a/android/Kordant/app/src/main/java/com/shieldai/android/data/model/Exposure.kt b/android/app/src/main/java/com/kordant/android/data/model/Exposure.kt similarity index 100% rename from android/Kordant/app/src/main/java/com/shieldai/android/data/model/Exposure.kt rename to android/app/src/main/java/com/kordant/android/data/model/Exposure.kt diff --git a/android/Kordant/app/src/main/java/com/shieldai/android/data/model/Property.kt b/android/app/src/main/java/com/kordant/android/data/model/Property.kt similarity index 100% rename from android/Kordant/app/src/main/java/com/shieldai/android/data/model/Property.kt rename to android/app/src/main/java/com/kordant/android/data/model/Property.kt diff --git a/android/Kordant/app/src/main/java/com/shieldai/android/data/model/RemovalRequest.kt b/android/app/src/main/java/com/kordant/android/data/model/RemovalRequest.kt similarity index 100% rename from android/Kordant/app/src/main/java/com/shieldai/android/data/model/RemovalRequest.kt rename to android/app/src/main/java/com/kordant/android/data/model/RemovalRequest.kt diff --git a/android/Kordant/app/src/main/java/com/shieldai/android/data/model/SpamRule.kt b/android/app/src/main/java/com/kordant/android/data/model/SpamRule.kt similarity index 100% rename from android/Kordant/app/src/main/java/com/shieldai/android/data/model/SpamRule.kt rename to android/app/src/main/java/com/kordant/android/data/model/SpamRule.kt diff --git a/android/Kordant/app/src/main/java/com/shieldai/android/data/model/Subscription.kt b/android/app/src/main/java/com/kordant/android/data/model/Subscription.kt similarity index 100% rename from android/Kordant/app/src/main/java/com/shieldai/android/data/model/Subscription.kt rename to android/app/src/main/java/com/kordant/android/data/model/Subscription.kt diff --git a/android/Kordant/app/src/main/java/com/shieldai/android/data/model/User.kt b/android/app/src/main/java/com/kordant/android/data/model/User.kt similarity index 100% rename from android/Kordant/app/src/main/java/com/shieldai/android/data/model/User.kt rename to android/app/src/main/java/com/kordant/android/data/model/User.kt diff --git a/android/Kordant/app/src/main/java/com/shieldai/android/data/model/VoiceAnalysis.kt b/android/app/src/main/java/com/kordant/android/data/model/VoiceAnalysis.kt similarity index 100% rename from android/Kordant/app/src/main/java/com/shieldai/android/data/model/VoiceAnalysis.kt rename to android/app/src/main/java/com/kordant/android/data/model/VoiceAnalysis.kt diff --git a/android/Kordant/app/src/main/java/com/shieldai/android/data/model/VoiceEnrollment.kt b/android/app/src/main/java/com/kordant/android/data/model/VoiceEnrollment.kt similarity index 100% rename from android/Kordant/app/src/main/java/com/shieldai/android/data/model/VoiceEnrollment.kt rename to android/app/src/main/java/com/kordant/android/data/model/VoiceEnrollment.kt diff --git a/android/Kordant/app/src/main/java/com/shieldai/android/data/model/WatchlistItem.kt b/android/app/src/main/java/com/kordant/android/data/model/WatchlistItem.kt similarity index 100% rename from android/Kordant/app/src/main/java/com/shieldai/android/data/model/WatchlistItem.kt rename to android/app/src/main/java/com/kordant/android/data/model/WatchlistItem.kt diff --git a/android/Kordant/app/src/main/java/com/shieldai/android/data/remote/AuthInterceptor.kt b/android/app/src/main/java/com/kordant/android/data/remote/AuthInterceptor.kt similarity index 100% rename from android/Kordant/app/src/main/java/com/shieldai/android/data/remote/AuthInterceptor.kt rename to android/app/src/main/java/com/kordant/android/data/remote/AuthInterceptor.kt diff --git a/android/Kordant/app/src/main/java/com/shieldai/android/data/remote/ErrorHandler.kt b/android/app/src/main/java/com/kordant/android/data/remote/ErrorHandler.kt similarity index 100% rename from android/Kordant/app/src/main/java/com/shieldai/android/data/remote/ErrorHandler.kt rename to android/app/src/main/java/com/kordant/android/data/remote/ErrorHandler.kt diff --git a/android/Kordant/app/src/main/java/com/shieldai/android/data/remote/TRPCApiService.kt b/android/app/src/main/java/com/kordant/android/data/remote/TRPCApiService.kt similarity index 100% rename from android/Kordant/app/src/main/java/com/shieldai/android/data/remote/TRPCApiService.kt rename to android/app/src/main/java/com/kordant/android/data/remote/TRPCApiService.kt diff --git a/android/Kordant/app/src/main/java/com/shieldai/android/data/remote/TRPCResponse.kt b/android/app/src/main/java/com/kordant/android/data/remote/TRPCResponse.kt similarity index 100% rename from android/Kordant/app/src/main/java/com/shieldai/android/data/remote/TRPCResponse.kt rename to android/app/src/main/java/com/kordant/android/data/remote/TRPCResponse.kt diff --git a/android/Kordant/app/src/main/java/com/shieldai/android/data/repository/AlertRepository.kt b/android/app/src/main/java/com/kordant/android/data/repository/AlertRepository.kt similarity index 100% rename from android/Kordant/app/src/main/java/com/shieldai/android/data/repository/AlertRepository.kt rename to android/app/src/main/java/com/kordant/android/data/repository/AlertRepository.kt diff --git a/android/Kordant/app/src/main/java/com/shieldai/android/data/repository/AuthRepository.kt b/android/app/src/main/java/com/kordant/android/data/repository/AuthRepository.kt similarity index 100% rename from android/Kordant/app/src/main/java/com/shieldai/android/data/repository/AuthRepository.kt rename to android/app/src/main/java/com/kordant/android/data/repository/AuthRepository.kt diff --git a/android/Kordant/app/src/main/java/com/shieldai/android/data/repository/DarkWatchRepository.kt b/android/app/src/main/java/com/kordant/android/data/repository/DarkWatchRepository.kt similarity index 100% rename from android/Kordant/app/src/main/java/com/shieldai/android/data/repository/DarkWatchRepository.kt rename to android/app/src/main/java/com/kordant/android/data/repository/DarkWatchRepository.kt diff --git a/android/Kordant/app/src/main/java/com/shieldai/android/data/repository/HomeTitleRepository.kt b/android/app/src/main/java/com/kordant/android/data/repository/HomeTitleRepository.kt similarity index 100% rename from android/Kordant/app/src/main/java/com/shieldai/android/data/repository/HomeTitleRepository.kt rename to android/app/src/main/java/com/kordant/android/data/repository/HomeTitleRepository.kt diff --git a/android/Kordant/app/src/main/java/com/shieldai/android/data/repository/RemoveBrokersRepository.kt b/android/app/src/main/java/com/kordant/android/data/repository/RemoveBrokersRepository.kt similarity index 100% rename from android/Kordant/app/src/main/java/com/shieldai/android/data/repository/RemoveBrokersRepository.kt rename to android/app/src/main/java/com/kordant/android/data/repository/RemoveBrokersRepository.kt diff --git a/android/Kordant/app/src/main/java/com/shieldai/android/data/repository/SpamShieldRepository.kt b/android/app/src/main/java/com/kordant/android/data/repository/SpamShieldRepository.kt similarity index 100% rename from android/Kordant/app/src/main/java/com/shieldai/android/data/repository/SpamShieldRepository.kt rename to android/app/src/main/java/com/kordant/android/data/repository/SpamShieldRepository.kt diff --git a/android/Kordant/app/src/main/java/com/shieldai/android/data/repository/SubscriptionRepository.kt b/android/app/src/main/java/com/kordant/android/data/repository/SubscriptionRepository.kt similarity index 100% rename from android/Kordant/app/src/main/java/com/shieldai/android/data/repository/SubscriptionRepository.kt rename to android/app/src/main/java/com/kordant/android/data/repository/SubscriptionRepository.kt diff --git a/android/Kordant/app/src/main/java/com/shieldai/android/data/repository/UserRepository.kt b/android/app/src/main/java/com/kordant/android/data/repository/UserRepository.kt similarity index 100% rename from android/Kordant/app/src/main/java/com/shieldai/android/data/repository/UserRepository.kt rename to android/app/src/main/java/com/kordant/android/data/repository/UserRepository.kt diff --git a/android/Kordant/app/src/main/java/com/shieldai/android/data/repository/VoicePrintRepository.kt b/android/app/src/main/java/com/kordant/android/data/repository/VoicePrintRepository.kt similarity index 100% rename from android/Kordant/app/src/main/java/com/shieldai/android/data/repository/VoicePrintRepository.kt rename to android/app/src/main/java/com/kordant/android/data/repository/VoicePrintRepository.kt diff --git a/android/Kordant/app/src/main/java/com/shieldai/android/data/sync/OfflineWorker.kt b/android/app/src/main/java/com/kordant/android/data/sync/OfflineWorker.kt similarity index 100% rename from android/Kordant/app/src/main/java/com/shieldai/android/data/sync/OfflineWorker.kt rename to android/app/src/main/java/com/kordant/android/data/sync/OfflineWorker.kt diff --git a/android/Kordant/app/src/main/java/com/shieldai/android/data/sync/PendingRequestQueue.kt b/android/app/src/main/java/com/kordant/android/data/sync/PendingRequestQueue.kt similarity index 100% rename from android/Kordant/app/src/main/java/com/shieldai/android/data/sync/PendingRequestQueue.kt rename to android/app/src/main/java/com/kordant/android/data/sync/PendingRequestQueue.kt diff --git a/android/Kordant/app/src/main/java/com/shieldai/android/data/sync/SyncManager.kt b/android/app/src/main/java/com/kordant/android/data/sync/SyncManager.kt similarity index 100% rename from android/Kordant/app/src/main/java/com/shieldai/android/data/sync/SyncManager.kt rename to android/app/src/main/java/com/kordant/android/data/sync/SyncManager.kt diff --git a/android/Kordant/app/src/main/java/com/shieldai/android/di/DatabaseModule.kt b/android/app/src/main/java/com/kordant/android/di/DatabaseModule.kt similarity index 100% rename from android/Kordant/app/src/main/java/com/shieldai/android/di/DatabaseModule.kt rename to android/app/src/main/java/com/kordant/android/di/DatabaseModule.kt diff --git a/android/Kordant/app/src/main/java/com/shieldai/android/di/NetworkModule.kt b/android/app/src/main/java/com/kordant/android/di/NetworkModule.kt similarity index 100% rename from android/Kordant/app/src/main/java/com/shieldai/android/di/NetworkModule.kt rename to android/app/src/main/java/com/kordant/android/di/NetworkModule.kt diff --git a/android/Kordant/app/src/main/java/com/shieldai/android/di/RepositoryModule.kt b/android/app/src/main/java/com/kordant/android/di/RepositoryModule.kt similarity index 100% rename from android/Kordant/app/src/main/java/com/shieldai/android/di/RepositoryModule.kt rename to android/app/src/main/java/com/kordant/android/di/RepositoryModule.kt diff --git a/android/Kordant/app/src/main/java/com/shieldai/android/navigation/AppNavigation.kt b/android/app/src/main/java/com/kordant/android/navigation/AppNavigation.kt similarity index 100% rename from android/Kordant/app/src/main/java/com/shieldai/android/navigation/AppNavigation.kt rename to android/app/src/main/java/com/kordant/android/navigation/AppNavigation.kt diff --git a/android/Kordant/app/src/main/java/com/shieldai/android/navigation/BottomNavBar.kt b/android/app/src/main/java/com/kordant/android/navigation/BottomNavBar.kt similarity index 100% rename from android/Kordant/app/src/main/java/com/shieldai/android/navigation/BottomNavBar.kt rename to android/app/src/main/java/com/kordant/android/navigation/BottomNavBar.kt diff --git a/android/Kordant/app/src/main/java/com/shieldai/android/navigation/NavGraph.kt b/android/app/src/main/java/com/kordant/android/navigation/NavGraph.kt similarity index 100% rename from android/Kordant/app/src/main/java/com/shieldai/android/navigation/NavGraph.kt rename to android/app/src/main/java/com/kordant/android/navigation/NavGraph.kt diff --git a/android/Kordant/app/src/main/java/com/shieldai/android/navigation/Screen.kt b/android/app/src/main/java/com/kordant/android/navigation/Screen.kt similarity index 100% rename from android/Kordant/app/src/main/java/com/shieldai/android/navigation/Screen.kt rename to android/app/src/main/java/com/kordant/android/navigation/Screen.kt diff --git a/android/Kordant/app/src/main/java/com/shieldai/android/ui/components/ComponentShowcase.kt b/android/app/src/main/java/com/kordant/android/ui/components/ComponentShowcase.kt similarity index 100% rename from android/Kordant/app/src/main/java/com/shieldai/android/ui/components/ComponentShowcase.kt rename to android/app/src/main/java/com/kordant/android/ui/components/ComponentShowcase.kt diff --git a/android/Kordant/app/src/main/java/com/shieldai/android/ui/components/ShieldAvatar.kt b/android/app/src/main/java/com/kordant/android/ui/components/ShieldAvatar.kt similarity index 100% rename from android/Kordant/app/src/main/java/com/shieldai/android/ui/components/ShieldAvatar.kt rename to android/app/src/main/java/com/kordant/android/ui/components/ShieldAvatar.kt diff --git a/android/Kordant/app/src/main/java/com/shieldai/android/ui/components/ShieldBadge.kt b/android/app/src/main/java/com/kordant/android/ui/components/ShieldBadge.kt similarity index 100% rename from android/Kordant/app/src/main/java/com/shieldai/android/ui/components/ShieldBadge.kt rename to android/app/src/main/java/com/kordant/android/ui/components/ShieldBadge.kt diff --git a/android/Kordant/app/src/main/java/com/shieldai/android/ui/components/ShieldButton.kt b/android/app/src/main/java/com/kordant/android/ui/components/ShieldButton.kt similarity index 100% rename from android/Kordant/app/src/main/java/com/shieldai/android/ui/components/ShieldButton.kt rename to android/app/src/main/java/com/kordant/android/ui/components/ShieldButton.kt diff --git a/android/Kordant/app/src/main/java/com/shieldai/android/ui/components/ShieldCard.kt b/android/app/src/main/java/com/kordant/android/ui/components/ShieldCard.kt similarity index 100% rename from android/Kordant/app/src/main/java/com/shieldai/android/ui/components/ShieldCard.kt rename to android/app/src/main/java/com/kordant/android/ui/components/ShieldCard.kt diff --git a/android/Kordant/app/src/main/java/com/shieldai/android/ui/components/ShieldEmptyState.kt b/android/app/src/main/java/com/kordant/android/ui/components/ShieldEmptyState.kt similarity index 100% rename from android/Kordant/app/src/main/java/com/shieldai/android/ui/components/ShieldEmptyState.kt rename to android/app/src/main/java/com/kordant/android/ui/components/ShieldEmptyState.kt diff --git a/android/Kordant/app/src/main/java/com/shieldai/android/ui/components/ShieldModal.kt b/android/app/src/main/java/com/kordant/android/ui/components/ShieldModal.kt similarity index 100% rename from android/Kordant/app/src/main/java/com/shieldai/android/ui/components/ShieldModal.kt rename to android/app/src/main/java/com/kordant/android/ui/components/ShieldModal.kt diff --git a/android/Kordant/app/src/main/java/com/shieldai/android/ui/components/ShieldProgressBar.kt b/android/app/src/main/java/com/kordant/android/ui/components/ShieldProgressBar.kt similarity index 100% rename from android/Kordant/app/src/main/java/com/shieldai/android/ui/components/ShieldProgressBar.kt rename to android/app/src/main/java/com/kordant/android/ui/components/ShieldProgressBar.kt diff --git a/android/Kordant/app/src/main/java/com/shieldai/android/ui/components/ShieldSkeleton.kt b/android/app/src/main/java/com/kordant/android/ui/components/ShieldSkeleton.kt similarity index 100% rename from android/Kordant/app/src/main/java/com/shieldai/android/ui/components/ShieldSkeleton.kt rename to android/app/src/main/java/com/kordant/android/ui/components/ShieldSkeleton.kt diff --git a/android/Kordant/app/src/main/java/com/shieldai/android/ui/components/ShieldTextField.kt b/android/app/src/main/java/com/kordant/android/ui/components/ShieldTextField.kt similarity index 100% rename from android/Kordant/app/src/main/java/com/shieldai/android/ui/components/ShieldTextField.kt rename to android/app/src/main/java/com/kordant/android/ui/components/ShieldTextField.kt diff --git a/android/Kordant/app/src/main/java/com/shieldai/android/ui/components/ShieldToast.kt b/android/app/src/main/java/com/kordant/android/ui/components/ShieldToast.kt similarity index 100% rename from android/Kordant/app/src/main/java/com/shieldai/android/ui/components/ShieldToast.kt rename to android/app/src/main/java/com/kordant/android/ui/components/ShieldToast.kt diff --git a/android/Kordant/app/src/main/java/com/shieldai/android/ui/components/ThreatGauge.kt b/android/app/src/main/java/com/kordant/android/ui/components/ThreatGauge.kt similarity index 100% rename from android/Kordant/app/src/main/java/com/shieldai/android/ui/components/ThreatGauge.kt rename to android/app/src/main/java/com/kordant/android/ui/components/ThreatGauge.kt diff --git a/android/Kordant/app/src/main/java/com/shieldai/android/ui/screens/auth/AuthScreen.kt b/android/app/src/main/java/com/kordant/android/ui/screens/auth/AuthScreen.kt similarity index 100% rename from android/Kordant/app/src/main/java/com/shieldai/android/ui/screens/auth/AuthScreen.kt rename to android/app/src/main/java/com/kordant/android/ui/screens/auth/AuthScreen.kt diff --git a/android/Kordant/app/src/main/java/com/shieldai/android/ui/screens/auth/BiometricAuthScreen.kt b/android/app/src/main/java/com/kordant/android/ui/screens/auth/BiometricAuthScreen.kt similarity index 100% rename from android/Kordant/app/src/main/java/com/shieldai/android/ui/screens/auth/BiometricAuthScreen.kt rename to android/app/src/main/java/com/kordant/android/ui/screens/auth/BiometricAuthScreen.kt diff --git a/android/Kordant/app/src/main/java/com/shieldai/android/ui/screens/auth/ForgotPasswordScreen.kt b/android/app/src/main/java/com/kordant/android/ui/screens/auth/ForgotPasswordScreen.kt similarity index 100% rename from android/Kordant/app/src/main/java/com/shieldai/android/ui/screens/auth/ForgotPasswordScreen.kt rename to android/app/src/main/java/com/kordant/android/ui/screens/auth/ForgotPasswordScreen.kt diff --git a/android/Kordant/app/src/main/java/com/shieldai/android/ui/screens/auth/LoginScreen.kt b/android/app/src/main/java/com/kordant/android/ui/screens/auth/LoginScreen.kt similarity index 100% rename from android/Kordant/app/src/main/java/com/shieldai/android/ui/screens/auth/LoginScreen.kt rename to android/app/src/main/java/com/kordant/android/ui/screens/auth/LoginScreen.kt diff --git a/android/Kordant/app/src/main/java/com/shieldai/android/ui/screens/auth/ResetPasswordScreen.kt b/android/app/src/main/java/com/kordant/android/ui/screens/auth/ResetPasswordScreen.kt similarity index 100% rename from android/Kordant/app/src/main/java/com/shieldai/android/ui/screens/auth/ResetPasswordScreen.kt rename to android/app/src/main/java/com/kordant/android/ui/screens/auth/ResetPasswordScreen.kt diff --git a/android/Kordant/app/src/main/java/com/shieldai/android/ui/screens/auth/SignupScreen.kt b/android/app/src/main/java/com/kordant/android/ui/screens/auth/SignupScreen.kt similarity index 100% rename from android/Kordant/app/src/main/java/com/shieldai/android/ui/screens/auth/SignupScreen.kt rename to android/app/src/main/java/com/kordant/android/ui/screens/auth/SignupScreen.kt diff --git a/android/Kordant/app/src/main/java/com/shieldai/android/ui/screens/dashboard/AlertDetailScreen.kt b/android/app/src/main/java/com/kordant/android/ui/screens/dashboard/AlertDetailScreen.kt similarity index 100% rename from android/Kordant/app/src/main/java/com/shieldai/android/ui/screens/dashboard/AlertDetailScreen.kt rename to android/app/src/main/java/com/kordant/android/ui/screens/dashboard/AlertDetailScreen.kt diff --git a/android/Kordant/app/src/main/java/com/shieldai/android/ui/screens/dashboard/DashboardScreen.kt b/android/app/src/main/java/com/kordant/android/ui/screens/dashboard/DashboardScreen.kt similarity index 100% rename from android/Kordant/app/src/main/java/com/shieldai/android/ui/screens/dashboard/DashboardScreen.kt rename to android/app/src/main/java/com/kordant/android/ui/screens/dashboard/DashboardScreen.kt diff --git a/android/Kordant/app/src/main/java/com/shieldai/android/ui/screens/onboarding/CompleteStep.kt b/android/app/src/main/java/com/kordant/android/ui/screens/onboarding/CompleteStep.kt similarity index 100% rename from android/Kordant/app/src/main/java/com/shieldai/android/ui/screens/onboarding/CompleteStep.kt rename to android/app/src/main/java/com/kordant/android/ui/screens/onboarding/CompleteStep.kt diff --git a/android/Kordant/app/src/main/java/com/shieldai/android/ui/screens/onboarding/FamilyInviteStep.kt b/android/app/src/main/java/com/kordant/android/ui/screens/onboarding/FamilyInviteStep.kt similarity index 100% rename from android/Kordant/app/src/main/java/com/shieldai/android/ui/screens/onboarding/FamilyInviteStep.kt rename to android/app/src/main/java/com/kordant/android/ui/screens/onboarding/FamilyInviteStep.kt diff --git a/android/Kordant/app/src/main/java/com/shieldai/android/ui/screens/onboarding/OnboardingScreen.kt b/android/app/src/main/java/com/kordant/android/ui/screens/onboarding/OnboardingScreen.kt similarity index 100% rename from android/Kordant/app/src/main/java/com/shieldai/android/ui/screens/onboarding/OnboardingScreen.kt rename to android/app/src/main/java/com/kordant/android/ui/screens/onboarding/OnboardingScreen.kt diff --git a/android/Kordant/app/src/main/java/com/shieldai/android/ui/screens/onboarding/PlanSelectionStep.kt b/android/app/src/main/java/com/kordant/android/ui/screens/onboarding/PlanSelectionStep.kt similarity index 100% rename from android/Kordant/app/src/main/java/com/shieldai/android/ui/screens/onboarding/PlanSelectionStep.kt rename to android/app/src/main/java/com/kordant/android/ui/screens/onboarding/PlanSelectionStep.kt diff --git a/android/Kordant/app/src/main/java/com/shieldai/android/ui/screens/onboarding/WatchlistSetupStep.kt b/android/app/src/main/java/com/kordant/android/ui/screens/onboarding/WatchlistSetupStep.kt similarity index 100% rename from android/Kordant/app/src/main/java/com/shieldai/android/ui/screens/onboarding/WatchlistSetupStep.kt rename to android/app/src/main/java/com/kordant/android/ui/screens/onboarding/WatchlistSetupStep.kt diff --git a/android/Kordant/app/src/main/java/com/shieldai/android/ui/screens/services/DarkWatchScreen.kt b/android/app/src/main/java/com/kordant/android/ui/screens/services/DarkWatchScreen.kt similarity index 100% rename from android/Kordant/app/src/main/java/com/shieldai/android/ui/screens/services/DarkWatchScreen.kt rename to android/app/src/main/java/com/kordant/android/ui/screens/services/DarkWatchScreen.kt diff --git a/android/Kordant/app/src/main/java/com/shieldai/android/ui/screens/services/HomeTitleScreen.kt b/android/app/src/main/java/com/kordant/android/ui/screens/services/HomeTitleScreen.kt similarity index 100% rename from android/Kordant/app/src/main/java/com/shieldai/android/ui/screens/services/HomeTitleScreen.kt rename to android/app/src/main/java/com/kordant/android/ui/screens/services/HomeTitleScreen.kt diff --git a/android/Kordant/app/src/main/java/com/shieldai/android/ui/screens/services/RemoveBrokersScreen.kt b/android/app/src/main/java/com/kordant/android/ui/screens/services/RemoveBrokersScreen.kt similarity index 100% rename from android/Kordant/app/src/main/java/com/shieldai/android/ui/screens/services/RemoveBrokersScreen.kt rename to android/app/src/main/java/com/kordant/android/ui/screens/services/RemoveBrokersScreen.kt diff --git a/android/Kordant/app/src/main/java/com/shieldai/android/ui/screens/services/SpamShieldScreen.kt b/android/app/src/main/java/com/kordant/android/ui/screens/services/SpamShieldScreen.kt similarity index 100% rename from android/Kordant/app/src/main/java/com/shieldai/android/ui/screens/services/SpamShieldScreen.kt rename to android/app/src/main/java/com/kordant/android/ui/screens/services/SpamShieldScreen.kt diff --git a/android/Kordant/app/src/main/java/com/shieldai/android/ui/screens/services/VoicePrintScreen.kt b/android/app/src/main/java/com/kordant/android/ui/screens/services/VoicePrintScreen.kt similarity index 100% rename from android/Kordant/app/src/main/java/com/shieldai/android/ui/screens/services/VoicePrintScreen.kt rename to android/app/src/main/java/com/kordant/android/ui/screens/services/VoicePrintScreen.kt diff --git a/android/Kordant/app/src/main/java/com/shieldai/android/ui/screens/settings/SettingsScreen.kt b/android/app/src/main/java/com/kordant/android/ui/screens/settings/SettingsScreen.kt similarity index 100% rename from android/Kordant/app/src/main/java/com/shieldai/android/ui/screens/settings/SettingsScreen.kt rename to android/app/src/main/java/com/kordant/android/ui/screens/settings/SettingsScreen.kt diff --git a/android/Kordant/app/src/main/java/com/shieldai/android/ui/theme/Color.kt b/android/app/src/main/java/com/kordant/android/ui/theme/Color.kt similarity index 100% rename from android/Kordant/app/src/main/java/com/shieldai/android/ui/theme/Color.kt rename to android/app/src/main/java/com/kordant/android/ui/theme/Color.kt diff --git a/android/Kordant/app/src/main/java/com/shieldai/android/ui/theme/Shape.kt b/android/app/src/main/java/com/kordant/android/ui/theme/Shape.kt similarity index 100% rename from android/Kordant/app/src/main/java/com/shieldai/android/ui/theme/Shape.kt rename to android/app/src/main/java/com/kordant/android/ui/theme/Shape.kt diff --git a/android/Kordant/app/src/main/java/com/shieldai/android/ui/theme/Theme.kt b/android/app/src/main/java/com/kordant/android/ui/theme/Theme.kt similarity index 100% rename from android/Kordant/app/src/main/java/com/shieldai/android/ui/theme/Theme.kt rename to android/app/src/main/java/com/kordant/android/ui/theme/Theme.kt diff --git a/android/Kordant/app/src/main/java/com/shieldai/android/ui/theme/Type.kt b/android/app/src/main/java/com/kordant/android/ui/theme/Type.kt similarity index 100% rename from android/Kordant/app/src/main/java/com/shieldai/android/ui/theme/Type.kt rename to android/app/src/main/java/com/kordant/android/ui/theme/Type.kt diff --git a/android/Kordant/app/src/main/java/com/shieldai/android/util/PasswordStrength.kt b/android/app/src/main/java/com/kordant/android/util/PasswordStrength.kt similarity index 100% rename from android/Kordant/app/src/main/java/com/shieldai/android/util/PasswordStrength.kt rename to android/app/src/main/java/com/kordant/android/util/PasswordStrength.kt diff --git a/android/Kordant/app/src/main/java/com/shieldai/android/viewmodel/AlertDetailViewModel.kt b/android/app/src/main/java/com/kordant/android/viewmodel/AlertDetailViewModel.kt similarity index 100% rename from android/Kordant/app/src/main/java/com/shieldai/android/viewmodel/AlertDetailViewModel.kt rename to android/app/src/main/java/com/kordant/android/viewmodel/AlertDetailViewModel.kt diff --git a/android/Kordant/app/src/main/java/com/shieldai/android/viewmodel/AuthViewModel.kt b/android/app/src/main/java/com/kordant/android/viewmodel/AuthViewModel.kt similarity index 100% rename from android/Kordant/app/src/main/java/com/shieldai/android/viewmodel/AuthViewModel.kt rename to android/app/src/main/java/com/kordant/android/viewmodel/AuthViewModel.kt diff --git a/android/Kordant/app/src/main/java/com/shieldai/android/viewmodel/DarkWatchViewModel.kt b/android/app/src/main/java/com/kordant/android/viewmodel/DarkWatchViewModel.kt similarity index 100% rename from android/Kordant/app/src/main/java/com/shieldai/android/viewmodel/DarkWatchViewModel.kt rename to android/app/src/main/java/com/kordant/android/viewmodel/DarkWatchViewModel.kt diff --git a/android/Kordant/app/src/main/java/com/shieldai/android/viewmodel/DashboardViewModel.kt b/android/app/src/main/java/com/kordant/android/viewmodel/DashboardViewModel.kt similarity index 100% rename from android/Kordant/app/src/main/java/com/shieldai/android/viewmodel/DashboardViewModel.kt rename to android/app/src/main/java/com/kordant/android/viewmodel/DashboardViewModel.kt diff --git a/android/Kordant/app/src/main/java/com/shieldai/android/viewmodel/HomeTitleViewModel.kt b/android/app/src/main/java/com/kordant/android/viewmodel/HomeTitleViewModel.kt similarity index 100% rename from android/Kordant/app/src/main/java/com/shieldai/android/viewmodel/HomeTitleViewModel.kt rename to android/app/src/main/java/com/kordant/android/viewmodel/HomeTitleViewModel.kt diff --git a/android/Kordant/app/src/main/java/com/shieldai/android/viewmodel/RemoveBrokersViewModel.kt b/android/app/src/main/java/com/kordant/android/viewmodel/RemoveBrokersViewModel.kt similarity index 100% rename from android/Kordant/app/src/main/java/com/shieldai/android/viewmodel/RemoveBrokersViewModel.kt rename to android/app/src/main/java/com/kordant/android/viewmodel/RemoveBrokersViewModel.kt diff --git a/android/Kordant/app/src/main/java/com/shieldai/android/viewmodel/SettingsViewModel.kt b/android/app/src/main/java/com/kordant/android/viewmodel/SettingsViewModel.kt similarity index 100% rename from android/Kordant/app/src/main/java/com/shieldai/android/viewmodel/SettingsViewModel.kt rename to android/app/src/main/java/com/kordant/android/viewmodel/SettingsViewModel.kt diff --git a/android/Kordant/app/src/main/java/com/shieldai/android/viewmodel/SpamShieldViewModel.kt b/android/app/src/main/java/com/kordant/android/viewmodel/SpamShieldViewModel.kt similarity index 100% rename from android/Kordant/app/src/main/java/com/shieldai/android/viewmodel/SpamShieldViewModel.kt rename to android/app/src/main/java/com/kordant/android/viewmodel/SpamShieldViewModel.kt diff --git a/android/Kordant/app/src/main/java/com/shieldai/android/viewmodel/VoicePrintViewModel.kt b/android/app/src/main/java/com/kordant/android/viewmodel/VoicePrintViewModel.kt similarity index 100% rename from android/Kordant/app/src/main/java/com/shieldai/android/viewmodel/VoicePrintViewModel.kt rename to android/app/src/main/java/com/kordant/android/viewmodel/VoicePrintViewModel.kt diff --git a/android/Kordant/app/src/main/res/drawable/ic_account_box.xml b/android/app/src/main/res/drawable/ic_account_box.xml similarity index 100% rename from android/Kordant/app/src/main/res/drawable/ic_account_box.xml rename to android/app/src/main/res/drawable/ic_account_box.xml diff --git a/android/Kordant/app/src/main/res/drawable/ic_alerts.xml b/android/app/src/main/res/drawable/ic_alerts.xml similarity index 100% rename from android/Kordant/app/src/main/res/drawable/ic_alerts.xml rename to android/app/src/main/res/drawable/ic_alerts.xml diff --git a/android/Kordant/app/src/main/res/drawable/ic_dashboard.xml b/android/app/src/main/res/drawable/ic_dashboard.xml similarity index 100% rename from android/Kordant/app/src/main/res/drawable/ic_dashboard.xml rename to android/app/src/main/res/drawable/ic_dashboard.xml diff --git a/android/Kordant/app/src/main/res/drawable/ic_home.xml b/android/app/src/main/res/drawable/ic_home.xml similarity index 100% rename from android/Kordant/app/src/main/res/drawable/ic_home.xml rename to android/app/src/main/res/drawable/ic_home.xml diff --git a/android/Kordant/app/src/main/res/drawable/ic_launcher_background.xml b/android/app/src/main/res/drawable/ic_launcher_background.xml similarity index 100% rename from android/Kordant/app/src/main/res/drawable/ic_launcher_background.xml rename to android/app/src/main/res/drawable/ic_launcher_background.xml diff --git a/android/Kordant/app/src/main/res/drawable/ic_launcher_foreground.xml b/android/app/src/main/res/drawable/ic_launcher_foreground.xml similarity index 100% rename from android/Kordant/app/src/main/res/drawable/ic_launcher_foreground.xml rename to android/app/src/main/res/drawable/ic_launcher_foreground.xml diff --git a/android/Kordant/app/src/main/res/drawable/ic_services.xml b/android/app/src/main/res/drawable/ic_services.xml similarity index 100% rename from android/Kordant/app/src/main/res/drawable/ic_services.xml rename to android/app/src/main/res/drawable/ic_services.xml diff --git a/android/Kordant/app/src/main/res/drawable/ic_settings.xml b/android/app/src/main/res/drawable/ic_settings.xml similarity index 100% rename from android/Kordant/app/src/main/res/drawable/ic_settings.xml rename to android/app/src/main/res/drawable/ic_settings.xml diff --git a/android/Kordant/app/src/main/res/mipmap-anydpi/ic_launcher.xml b/android/app/src/main/res/mipmap-anydpi/ic_launcher.xml similarity index 100% rename from android/Kordant/app/src/main/res/mipmap-anydpi/ic_launcher.xml rename to android/app/src/main/res/mipmap-anydpi/ic_launcher.xml diff --git a/android/Kordant/app/src/main/res/mipmap-anydpi/ic_launcher_round.xml b/android/app/src/main/res/mipmap-anydpi/ic_launcher_round.xml similarity index 100% rename from android/Kordant/app/src/main/res/mipmap-anydpi/ic_launcher_round.xml rename to android/app/src/main/res/mipmap-anydpi/ic_launcher_round.xml diff --git a/android/Kordant/app/src/main/res/mipmap-hdpi/ic_launcher.webp b/android/app/src/main/res/mipmap-hdpi/ic_launcher.webp similarity index 100% rename from android/Kordant/app/src/main/res/mipmap-hdpi/ic_launcher.webp rename to android/app/src/main/res/mipmap-hdpi/ic_launcher.webp diff --git a/android/Kordant/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp b/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp similarity index 100% rename from android/Kordant/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp rename to android/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp diff --git a/android/Kordant/app/src/main/res/mipmap-mdpi/ic_launcher.webp b/android/app/src/main/res/mipmap-mdpi/ic_launcher.webp similarity index 100% rename from android/Kordant/app/src/main/res/mipmap-mdpi/ic_launcher.webp rename to android/app/src/main/res/mipmap-mdpi/ic_launcher.webp diff --git a/android/Kordant/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp b/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp similarity index 100% rename from android/Kordant/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp rename to android/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp diff --git a/android/Kordant/app/src/main/res/mipmap-xhdpi/ic_launcher.webp b/android/app/src/main/res/mipmap-xhdpi/ic_launcher.webp similarity index 100% rename from android/Kordant/app/src/main/res/mipmap-xhdpi/ic_launcher.webp rename to android/app/src/main/res/mipmap-xhdpi/ic_launcher.webp diff --git a/android/Kordant/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp b/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp similarity index 100% rename from android/Kordant/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp rename to android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp diff --git a/android/Kordant/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp b/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp similarity index 100% rename from android/Kordant/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp rename to android/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp diff --git a/android/Kordant/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp b/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp similarity index 100% rename from android/Kordant/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp rename to android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp diff --git a/android/Kordant/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp similarity index 100% rename from android/Kordant/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp rename to android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp diff --git a/android/Kordant/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp similarity index 100% rename from android/Kordant/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp rename to android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp diff --git a/android/Kordant/app/src/main/res/values/colors.xml b/android/app/src/main/res/values/colors.xml similarity index 100% rename from android/Kordant/app/src/main/res/values/colors.xml rename to android/app/src/main/res/values/colors.xml diff --git a/android/Kordant/app/src/main/res/values/strings.xml b/android/app/src/main/res/values/strings.xml similarity index 100% rename from android/Kordant/app/src/main/res/values/strings.xml rename to android/app/src/main/res/values/strings.xml diff --git a/android/Kordant/app/src/main/res/values/themes.xml b/android/app/src/main/res/values/themes.xml similarity index 100% rename from android/Kordant/app/src/main/res/values/themes.xml rename to android/app/src/main/res/values/themes.xml diff --git a/android/Kordant/app/src/main/res/xml/backup_rules.xml b/android/app/src/main/res/xml/backup_rules.xml similarity index 100% rename from android/Kordant/app/src/main/res/xml/backup_rules.xml rename to android/app/src/main/res/xml/backup_rules.xml diff --git a/android/Kordant/app/src/main/res/xml/data_extraction_rules.xml b/android/app/src/main/res/xml/data_extraction_rules.xml similarity index 100% rename from android/Kordant/app/src/main/res/xml/data_extraction_rules.xml rename to android/app/src/main/res/xml/data_extraction_rules.xml diff --git a/android/Kordant/app/src/test/java/com/shieldai/android/ExampleUnitTest.kt b/android/app/src/test/java/com/shieldai/android/ExampleUnitTest.kt similarity index 100% rename from android/Kordant/app/src/test/java/com/shieldai/android/ExampleUnitTest.kt rename to android/app/src/test/java/com/shieldai/android/ExampleUnitTest.kt diff --git a/android/Kordant/app/src/test/java/com/shieldai/android/data/local/CacheManagerTest.kt b/android/app/src/test/java/com/shieldai/android/data/local/CacheManagerTest.kt similarity index 100% rename from android/Kordant/app/src/test/java/com/shieldai/android/data/local/CacheManagerTest.kt rename to android/app/src/test/java/com/shieldai/android/data/local/CacheManagerTest.kt diff --git a/android/Kordant/app/src/test/java/com/shieldai/android/data/remote/ErrorHandlerTest.kt b/android/app/src/test/java/com/shieldai/android/data/remote/ErrorHandlerTest.kt similarity index 100% rename from android/Kordant/app/src/test/java/com/shieldai/android/data/remote/ErrorHandlerTest.kt rename to android/app/src/test/java/com/shieldai/android/data/remote/ErrorHandlerTest.kt diff --git a/android/Kordant/app/src/test/java/com/shieldai/android/data/remote/TRPCResponseTest.kt b/android/app/src/test/java/com/shieldai/android/data/remote/TRPCResponseTest.kt similarity index 100% rename from android/Kordant/app/src/test/java/com/shieldai/android/data/remote/TRPCResponseTest.kt rename to android/app/src/test/java/com/shieldai/android/data/remote/TRPCResponseTest.kt diff --git a/android/Kordant/app/src/test/java/com/shieldai/android/data/sync/SyncManagerTest.kt b/android/app/src/test/java/com/shieldai/android/data/sync/SyncManagerTest.kt similarity index 100% rename from android/Kordant/app/src/test/java/com/shieldai/android/data/sync/SyncManagerTest.kt rename to android/app/src/test/java/com/shieldai/android/data/sync/SyncManagerTest.kt diff --git a/android/Kordant/app/src/test/java/com/shieldai/android/util/PasswordStrengthTest.kt b/android/app/src/test/java/com/shieldai/android/util/PasswordStrengthTest.kt similarity index 100% rename from android/Kordant/app/src/test/java/com/shieldai/android/util/PasswordStrengthTest.kt rename to android/app/src/test/java/com/shieldai/android/util/PasswordStrengthTest.kt diff --git a/android/Kordant/app/src/test/java/com/shieldai/android/viewmodel/AuthViewModelTest.kt b/android/app/src/test/java/com/shieldai/android/viewmodel/AuthViewModelTest.kt similarity index 100% rename from android/Kordant/app/src/test/java/com/shieldai/android/viewmodel/AuthViewModelTest.kt rename to android/app/src/test/java/com/shieldai/android/viewmodel/AuthViewModelTest.kt diff --git a/android/Kordant/app/src/test/java/com/shieldai/android/viewmodel/ServiceViewModelsTest.kt b/android/app/src/test/java/com/shieldai/android/viewmodel/ServiceViewModelsTest.kt similarity index 100% rename from android/Kordant/app/src/test/java/com/shieldai/android/viewmodel/ServiceViewModelsTest.kt rename to android/app/src/test/java/com/shieldai/android/viewmodel/ServiceViewModelsTest.kt diff --git a/android/Kordant/build.gradle.kts b/android/build.gradle.kts similarity index 100% rename from android/Kordant/build.gradle.kts rename to android/build.gradle.kts diff --git a/android/Kordant/gradle.properties b/android/gradle.properties similarity index 100% rename from android/Kordant/gradle.properties rename to android/gradle.properties diff --git a/android/Kordant/gradle/gradle-daemon-jvm.properties b/android/gradle/gradle-daemon-jvm.properties similarity index 100% rename from android/Kordant/gradle/gradle-daemon-jvm.properties rename to android/gradle/gradle-daemon-jvm.properties diff --git a/android/Kordant/gradle/libs.versions.toml b/android/gradle/libs.versions.toml similarity index 100% rename from android/Kordant/gradle/libs.versions.toml rename to android/gradle/libs.versions.toml diff --git a/android/Kordant/gradle/wrapper/gradle-wrapper.jar b/android/gradle/wrapper/gradle-wrapper.jar similarity index 100% rename from android/Kordant/gradle/wrapper/gradle-wrapper.jar rename to android/gradle/wrapper/gradle-wrapper.jar diff --git a/android/Kordant/gradle/wrapper/gradle-wrapper.properties b/android/gradle/wrapper/gradle-wrapper.properties similarity index 100% rename from android/Kordant/gradle/wrapper/gradle-wrapper.properties rename to android/gradle/wrapper/gradle-wrapper.properties diff --git a/android/Kordant/gradlew b/android/gradlew similarity index 100% rename from android/Kordant/gradlew rename to android/gradlew diff --git a/android/Kordant/gradlew.bat b/android/gradlew.bat similarity index 100% rename from android/Kordant/gradlew.bat rename to android/gradlew.bat diff --git a/android/local.properties b/android/local.properties new file mode 100644 index 0000000..ef613a0 --- /dev/null +++ b/android/local.properties @@ -0,0 +1,10 @@ +## This file is automatically generated by Android Studio. +# Do not modify this file -- YOUR CHANGES WILL BE ERASED! +# +# This file should *NOT* be checked into Version Control Systems, +# as it contains information specific to your local configuration. +# +# Location of the SDK. This is only used by Gradle. +# For customization when using a Version Control System, please read the +# header note. +sdk.dir=/Users/mike/Library/Android/sdk \ No newline at end of file diff --git a/android/Kordant/settings.gradle.kts b/android/settings.gradle.kts similarity index 100% rename from android/Kordant/settings.gradle.kts rename to android/settings.gradle.kts diff --git a/bun.lock b/bun.lock index fbbd823..8d95a11 100644 --- a/bun.lock +++ b/bun.lock @@ -3,7 +3,7 @@ "configVersion": 1, "workspaces": { "": { - "name": "shieldai", + "name": "kordant", "devDependencies": { "@types/node": "^25.6.0", "@vitest/coverage-v8": "^4.1.5", diff --git a/iOS/Kordant.xcodeproj/project.pbxproj b/iOS/Kordant.xcodeproj/project.pbxproj new file mode 100644 index 0000000..4f9a46b --- /dev/null +++ b/iOS/Kordant.xcodeproj/project.pbxproj @@ -0,0 +1,594 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 77; + objects = { + +/* Begin PBXContainerItemProxy section */ + 277FB3A02FC4A75E0090D71F /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 277FB3882FC4A75C0090D71F /* Project object */; + proxyType = 1; + remoteGlobalIDString = 277FB38F2FC4A75C0090D71F; + remoteInfo = Kordant; + }; + 277FB3AA2FC4A75E0090D71F /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 277FB3882FC4A75C0090D71F /* Project object */; + proxyType = 1; + remoteGlobalIDString = 277FB38F2FC4A75C0090D71F; + remoteInfo = Kordant; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXFileReference section */ + 277FB3902FC4A75C0090D71F /* Kordant.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Kordant.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 277FB39F2FC4A75E0090D71F /* KordantTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = KordantTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 277FB3A92FC4A75E0090D71F /* KordantUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = KordantUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; +/* End PBXFileReference section */ + +/* Begin PBXFileSystemSynchronizedRootGroup section */ + 277FB3922FC4A75C0090D71F /* Kordant */ = { + isa = PBXFileSystemSynchronizedRootGroup; + path = Kordant; + sourceTree = ""; + }; + 277FB3A22FC4A75E0090D71F /* KordantTests */ = { + isa = PBXFileSystemSynchronizedRootGroup; + path = KordantTests; + sourceTree = ""; + }; + 277FB3AC2FC4A75E0090D71F /* KordantUITests */ = { + isa = PBXFileSystemSynchronizedRootGroup; + path = KordantUITests; + sourceTree = ""; + }; +/* End PBXFileSystemSynchronizedRootGroup section */ + +/* Begin PBXFrameworksBuildPhase section */ + 277FB38D2FC4A75C0090D71F /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 277FB39C2FC4A75E0090D71F /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 277FB3A62FC4A75E0090D71F /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 277FB3872FC4A75C0090D71F = { + isa = PBXGroup; + children = ( + 277FB3922FC4A75C0090D71F /* Kordant */, + 277FB3A22FC4A75E0090D71F /* KordantTests */, + 277FB3AC2FC4A75E0090D71F /* KordantUITests */, + 277FB3912FC4A75C0090D71F /* Products */, + ); + sourceTree = ""; + }; + 277FB3912FC4A75C0090D71F /* Products */ = { + isa = PBXGroup; + children = ( + 277FB3902FC4A75C0090D71F /* Kordant.app */, + 277FB39F2FC4A75E0090D71F /* KordantTests.xctest */, + 277FB3A92FC4A75E0090D71F /* KordantUITests.xctest */, + ); + name = Products; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 277FB38F2FC4A75C0090D71F /* Kordant */ = { + isa = PBXNativeTarget; + buildConfigurationList = 277FB3B32FC4A75E0090D71F /* Build configuration list for PBXNativeTarget "Kordant" */; + buildPhases = ( + 277FB38C2FC4A75C0090D71F /* Sources */, + 277FB38D2FC4A75C0090D71F /* Frameworks */, + 277FB38E2FC4A75C0090D71F /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + fileSystemSynchronizedGroups = ( + 277FB3922FC4A75C0090D71F /* Kordant */, + ); + name = Kordant; + packageProductDependencies = ( + ); + productName = Kordant; + productReference = 277FB3902FC4A75C0090D71F /* Kordant.app */; + productType = "com.apple.product-type.application"; + }; + 277FB39E2FC4A75E0090D71F /* KordantTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 277FB3B62FC4A75E0090D71F /* Build configuration list for PBXNativeTarget "KordantTests" */; + buildPhases = ( + 277FB39B2FC4A75E0090D71F /* Sources */, + 277FB39C2FC4A75E0090D71F /* Frameworks */, + 277FB39D2FC4A75E0090D71F /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 277FB3A12FC4A75E0090D71F /* PBXTargetDependency */, + ); + fileSystemSynchronizedGroups = ( + 277FB3A22FC4A75E0090D71F /* KordantTests */, + ); + name = KordantTests; + packageProductDependencies = ( + ); + productName = KordantTests; + productReference = 277FB39F2FC4A75E0090D71F /* KordantTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; + 277FB3A82FC4A75E0090D71F /* KordantUITests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 277FB3B92FC4A75E0090D71F /* Build configuration list for PBXNativeTarget "KordantUITests" */; + buildPhases = ( + 277FB3A52FC4A75E0090D71F /* Sources */, + 277FB3A62FC4A75E0090D71F /* Frameworks */, + 277FB3A72FC4A75E0090D71F /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 277FB3AB2FC4A75E0090D71F /* PBXTargetDependency */, + ); + fileSystemSynchronizedGroups = ( + 277FB3AC2FC4A75E0090D71F /* KordantUITests */, + ); + name = KordantUITests; + packageProductDependencies = ( + ); + productName = KordantUITests; + productReference = 277FB3A92FC4A75E0090D71F /* KordantUITests.xctest */; + productType = "com.apple.product-type.bundle.ui-testing"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 277FB3882FC4A75C0090D71F /* Project object */ = { + isa = PBXProject; + attributes = { + BuildIndependentTargetsInParallel = 1; + LastSwiftUpdateCheck = 2650; + LastUpgradeCheck = 2650; + TargetAttributes = { + 277FB38F2FC4A75C0090D71F = { + CreatedOnToolsVersion = 26.5; + }; + 277FB39E2FC4A75E0090D71F = { + CreatedOnToolsVersion = 26.5; + TestTargetID = 277FB38F2FC4A75C0090D71F; + }; + 277FB3A82FC4A75E0090D71F = { + CreatedOnToolsVersion = 26.5; + TestTargetID = 277FB38F2FC4A75C0090D71F; + }; + }; + }; + buildConfigurationList = 277FB38B2FC4A75C0090D71F /* Build configuration list for PBXProject "Kordant" */; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 277FB3872FC4A75C0090D71F; + minimizedProjectReferenceProxies = 1; + preferredProjectObjectVersion = 77; + productRefGroup = 277FB3912FC4A75C0090D71F /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 277FB38F2FC4A75C0090D71F /* Kordant */, + 277FB39E2FC4A75E0090D71F /* KordantTests */, + 277FB3A82FC4A75E0090D71F /* KordantUITests */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 277FB38E2FC4A75C0090D71F /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 277FB39D2FC4A75E0090D71F /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 277FB3A72FC4A75E0090D71F /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 277FB38C2FC4A75C0090D71F /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 277FB39B2FC4A75E0090D71F /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 277FB3A52FC4A75E0090D71F /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + 277FB3A12FC4A75E0090D71F /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 277FB38F2FC4A75C0090D71F /* Kordant */; + targetProxy = 277FB3A02FC4A75E0090D71F /* PBXContainerItemProxy */; + }; + 277FB3AB2FC4A75E0090D71F /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 277FB38F2FC4A75C0090D71F /* Kordant */; + targetProxy = 277FB3AA2FC4A75E0090D71F /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin XCBuildConfiguration section */ + 277FB3B12FC4A75E0090D71F /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + DEVELOPMENT_TEAM = 6GK4F9L62V; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + ENABLE_USER_SCRIPT_SANDBOXING = YES; + GCC_C_LANGUAGE_STANDARD = gnu17; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + IPHONEOS_DEPLOYMENT_TARGET = 16.0; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + }; + name = Debug; + }; + 277FB3B22FC4A75E0090D71F /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + DEVELOPMENT_TEAM = 6GK4F9L62V; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = YES; + GCC_C_LANGUAGE_STANDARD = gnu17; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + IPHONEOS_DEPLOYMENT_TARGET = 16.0; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + SDKROOT = iphoneos; + SWIFT_COMPILATION_MODE = wholemodule; + }; + name = Release; + }; + 277FB3B42FC4A75E0090D71F /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CODE_SIGN_ENTITLEMENTS = Kordant/Kordant.entitlements; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = 6GK4F9L62V; + ENABLE_PREVIEWS = YES; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_KEY_CFBundleURLTypes = ( + { + CFBundleURLSchemes = (kordant); + CFBundleURLName = "com.mikefreno.Kordant"; + }, + ); + INFOPLIST_KEY_NSHumanReadableCopyright = ""; + INFOPLIST_KEY_NSMicrophoneUsageDescription = "Kordant needs microphone access to enroll your voice for clone detection."; + INFOPLIST_KEY_NSCameraUsageDescription = "Kordant uses the camera to scan documents and verify your identity."; + INFOPLIST_KEY_NSFaceIDUsageDescription = "Use Face ID to securely access your Kordant account."; + INFOPLIST_KEY_UIBackgroundModes = (remote-notification); + IPHONEOS_DEPLOYMENT_TARGET = 16.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.mikefreno.Kordant; + PRODUCT_NAME = "$(TARGET_NAME)"; + SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; + SWIFT_APPROACHABLE_CONCURRENCY = YES; + SWIFT_DEFAULT_ACTOR_ISOLATION = MainActor; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 277FB3B52FC4A75E0090D71F /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CODE_SIGN_ENTITLEMENTS = Kordant/Kordant.entitlements; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = 6GK4F9L62V; + ENABLE_PREVIEWS = YES; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_KEY_CFBundleURLTypes = ( + { + CFBundleURLSchemes = (kordant); + CFBundleURLName = "com.mikefreno.Kordant"; + }, + ); + INFOPLIST_KEY_NSHumanReadableCopyright = ""; + INFOPLIST_KEY_NSMicrophoneUsageDescription = "Kordant needs microphone access to enroll your voice for clone detection."; + INFOPLIST_KEY_NSCameraUsageDescription = "Kordant uses the camera to scan documents and verify your identity."; + INFOPLIST_KEY_NSFaceIDUsageDescription = "Use Face ID to securely access your Kordant account."; + INFOPLIST_KEY_UIBackgroundModes = (remote-notification); + IPHONEOS_DEPLOYMENT_TARGET = 16.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.mikefreno.Kordant; + PRODUCT_NAME = "$(TARGET_NAME)"; + SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; + SWIFT_APPROACHABLE_CONCURRENCY = YES; + SWIFT_DEFAULT_ACTOR_ISOLATION = MainActor; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Release; + }; + 277FB3B72FC4A75E0090D71F /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = 6GK4F9L62V; + GENERATE_INFOPLIST_FILE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 16.0; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.mikefreno.KordantTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + STRING_CATALOG_GENERATE_SYMBOLS = NO; + SWIFT_APPROACHABLE_CONCURRENCY = YES; + SWIFT_EMIT_LOC_STRINGS = NO; + SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Kordant.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Kordant"; + }; + name = Debug; + }; + 277FB3B82FC4A75E0090D71F /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = 6GK4F9L62V; + GENERATE_INFOPLIST_FILE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 16.0; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.mikefreno.KordantTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + STRING_CATALOG_GENERATE_SYMBOLS = NO; + SWIFT_APPROACHABLE_CONCURRENCY = YES; + SWIFT_EMIT_LOC_STRINGS = NO; + SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Kordant.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Kordant"; + }; + name = Release; + }; + 277FB3BA2FC4A75E0090D71F /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = 6GK4F9L62V; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.mikefreno.KordantUITests; + PRODUCT_NAME = "$(TARGET_NAME)"; + STRING_CATALOG_GENERATE_SYMBOLS = NO; + SWIFT_APPROACHABLE_CONCURRENCY = YES; + SWIFT_EMIT_LOC_STRINGS = NO; + SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES; + SWIFT_VERSION = 5.0; + TEST_TARGET_NAME = Kordant; + }; + name = Debug; + }; + 277FB3BB2FC4A75E0090D71F /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = 6GK4F9L62V; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.mikefreno.KordantUITests; + PRODUCT_NAME = "$(TARGET_NAME)"; + STRING_CATALOG_GENERATE_SYMBOLS = NO; + SWIFT_APPROACHABLE_CONCURRENCY = YES; + SWIFT_EMIT_LOC_STRINGS = NO; + SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES; + SWIFT_VERSION = 5.0; + TEST_TARGET_NAME = Kordant; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 277FB38B2FC4A75C0090D71F /* Build configuration list for PBXProject "Kordant" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 277FB3B12FC4A75E0090D71F /* Debug */, + 277FB3B22FC4A75E0090D71F /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 277FB3B32FC4A75E0090D71F /* Build configuration list for PBXNativeTarget "Kordant" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 277FB3B42FC4A75E0090D71F /* Debug */, + 277FB3B52FC4A75E0090D71F /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 277FB3B62FC4A75E0090D71F /* Build configuration list for PBXNativeTarget "KordantTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 277FB3B72FC4A75E0090D71F /* Debug */, + 277FB3B82FC4A75E0090D71F /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 277FB3B92FC4A75E0090D71F /* Build configuration list for PBXNativeTarget "KordantUITests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 277FB3BA2FC4A75E0090D71F /* Debug */, + 277FB3BB2FC4A75E0090D71F /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 277FB3882FC4A75C0090D71F /* Project object */; +} diff --git a/iOS/Kordant.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/iOS/Kordant.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..919434a --- /dev/null +++ b/iOS/Kordant.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/iOS/Kordant.xcodeproj/project.xcworkspace/xcuserdata/mike.xcuserdatad/UserInterfaceState.xcuserstate b/iOS/Kordant.xcodeproj/project.xcworkspace/xcuserdata/mike.xcuserdatad/UserInterfaceState.xcuserstate new file mode 100644 index 0000000000000000000000000000000000000000..bcb17aafdb84a873f6ad5ad052b1843696f7a384 GIT binary patch literal 11847 zcmd5?34Bw<)}NW%CTW%?DQ(&`OS3cqf$j?}Aa+R$6)1&JplmU1Zrebblq7|MJeYzY zo49`pD$o`bMG+7YLEJ!Go-6QBL|jpMuDCy++js80P1;I*pYQv<-}n2-uiV_3GiT1s zobx}+q`uYX4TZC_Pa%v5Vo(fU@YkiggAL7Ic$=CY4z$il57f_N z8^WOkgja8Ksg%{u@$6#OQ`$rH8j_&6T4&fDW+{G9jRzqalA~mlg0!dr6`~QS2oQE$_MpA!QFI6$M$e$*=vnj}dLF%mUPY(SY4kRF2fd3vKcm}S+Gx01u z8_&UWu?t^?-MAh%U=L<-GhT}Sf|udtnBoq+0b(-W>JGnl`@5G00ApJ-f$oo5w~{A zgpf;>>R=n)ZN8p9-j+g@fgsm8yR;z;w&-kVW?NWT+>|!IgAIkfes>sbl{dmLSZ7<1 zo#qXB>%Bg2c(F@mYYYS%*s?aS&jWl)eZIbgp{mAemi4e6Fy@FH)4hIAU=fHisKW2A z_pwu(gRI*#*6s7LLEx7v^p^&MZhsR9;txC6`nIMje`CO1~lGXB+=8n($DY7037ZJ+|&;v+&_h3Zj$Cvwx&PSim4)X;ZZoB1u& zxm1(dGrYpA87!}=8J-@~f_<&`x&8B_4Os<6h2=SpJV(Ze!s6nL{K|sDjN;;aFt^;o z!m_+)4x-xf(4MCW$mGN9$4kE<{lWT7g!gYtSmR8eNOl zptWcnHB$?<(sXL0b~>02p_kF2bl5Jm0p)>B4@Wnk8_`X?jo%8EJshk!8$LO-jF$5j z?hx!cwRfk5bIJ_yN)F3$sq~k2HU}0>gdJ{i!v>fK?B))wJOB>Z-2^6|HXu!ve<5e@ zJ{FAaQZ7FC;y_!tqCL#|IhPJJ)=RlG1Oq-F4>p;B#>Nl}*f|8$P*_AwkX;DYFcCy4 zZEFWufuj+6_z?q58tTK)=kTHos>I8v-y6P3XUgJWTt-ZebUG*BP1MG4v$TbfY8aar6W&poMfqH+l*^ zjgHbHT1-cA6B#dAR>)pZ3MP5(1*`><5grGGl+SI^ehd0E%bCAUyARBb4?Ehkf zuox%!9M)Rm*@n1(C(sLkr+-B+f|X^qwRt`Di^FVal1laNydig=er3|ZeT7wR1-`@w z=}5s^Uq-J?1JJ9ja&)1W(c}~8H5h;5(($QSQgh|`icJr&Iki8jGn*sh7ofj&aaJAK zrq|J%+&R2~&d@RRiY{~(y+uoCDIGnFQ=BTlhizv=b0ferf%P|qn}sue4>`8cB(*5U z`lN|p3ySdk2z}h&_CG zxl#1v9Y!~n_Hh|lfz_Z%I36ppicX?6baFRNz=LoiokFM4nY<=}WS96}AyNoIjP6nu z3`8)&9SVbc^z`@5Ws8M@{5bnS6v6zl}Yltr7sEj3dw zok!@G@ukU`Ag?L!hO#EzAZ-@~_PS&|>~DXA0wT3o{F*Pjolr6bvsEOs)}2 zw$X`^P4?m-%HN6S;rZByTd*Goa4TLw1GJScpg|g-sMTNSE=EXE1)v;Mi!*2*Bj1w?7kFwPyC=xqveLG3iRuZ;~=1_LeC zP5@;9WG?XGy(y@_8&EvTV5+;m%pIKW4)HO)kDcxfH#-4*ATD>;Lqq_CLVO)ff1tgu zLm0!cilj#U`^p@Jg^r>kM@DW%C1gj1mH8Q^xur!JrIkhbj{LmJ^75iemrC0!OCaC_ z=TsGu1rnLwK3>i~U16-0$AASh$$NT77x{j`g|6%6DO0N&> zhS#*R#hfAXAg|^eL3Adr8BDX*4;j<|#3=#~)>B*6pHD=d$M6xPf$$yz98N#_@pT4Z z{~>;i&_0Eaf{Vou(RH2p7~Mcep8!mM7C(odhZqY&K+Y~|xH8ogwl&CxAlu{thi;_T zO;Y2d^^p=5CQMAM$dnET166`>X+r}580+DjNu4*$w(ye95u$O{Qw1%dxk8Xy69|Du z1pIlNJKFn}>Fu3jg(^{#syCjH#7X5-TixO2I!KAx_HmGycFLj-O@&V2c%^(U+urKt zLu-Bxp3Q@2Q)4 zi%?#%3MdWoE}0Bk(;}^dN;=KZZW+`qlUYG&F3DZHY(u*2gRMezgith4>bh*GwPeA& ze7m(|O;5iBYAfj|6RLCL(Nw6$)uSee)IyL*ErW#VCaA(~gUZ`ph?x&UBzqicZD-Lr z^ge`D7ooQH4~#K`B~Vv0VJo(SXD-5{utVTYaL*jWdY(NG+!F)6h+hKKt7vca30OF( zEzE^~Tox`=7db`fQMhjWGJXY3{?qg>RlRF!cx?H!oI=O6sl9VRlAT-F``Ty9sf%0L z(1@PtT+^JcyqsP-{+OKrHU6e54=AL!(F=ihM4?~9C%NF@1ihgXZv%q|qq7Zc?z}Z; zLTJ&(*KDjMttI@XL^z$nZ$XBO-^6F>&GeQod=9@&Z>9gmaU9$RH4RA18rfhFv^fG` zJjZCJSLNXH!G4tGU=B?I$lfB^AkYX7@&i7W7QTPPJIknkMCADde~0q#!=K{M@aOmo z{3X7KzrtVRZ}7Kt6WvVjpm)-{=oY$_Zlj&F>puKF8ixOYf5iX9KjEM8FZfsd8|{Wn zYzMuY-a`-4&pL2i2bOkV8IOq(!0W|XCm45_PXKDb23@M5Wh}%ikPf%BLNpQz_<5k> z$~!&2Q5Y(~m#OCkvNvxl_*RZrO%PJ;;9?M1oq31ZUTJfq~&z~ zB#{#ZQj&PIn<#;x+8NysArk_;>(j=^6)H@(^HD8PlLV-YMMrdzL1=Q{1_;}Py>Lxn z8^corQ@l;hVHG+}l1U2D!nCPGNA$!%j3kYi&@h-RQY4H-esgr3SsIZwiBp1pQaMHba zB%}pB`|AyMkvtBS{v}OBh5|H$nUm z6n3|?(tY$HivH8}PEtW2^cw_t89ANEiFc8)+$sm^0^d!l$dzRLOwMTvyF(Aqhh3`T zf3pgk+0K=JT7!Xk!sb_#NwYwfh&pwX8ZsGlxla=~&>DBceAdIMTLsiZ77HVtWU5fh z>Zd!sXV~AyyGSj!IIi~fr|1P$n+^#*nL!_oW*B4^f%@v*WHy-x;^e`ag zWAq4p{BAUycnAxhCO)fpf<6hKr-WeE0Fi8)&%?#DHIPtDVHZHE5C+iWO6{RpoaT@B z`aKZ0&FMw3oIEg2weSuxK4c6%kfN*#3GY1+3ELFm;1bAHOb@`5Ac2xhO8y)z(6;U>&QmZPHrH>Q8}6cSP{|ve#gSqV!+2i zoXT=}6X&*j>Hz(Z6;=(l67Ex4cByoJK3nJkli$-1V)=r1KiOf2`;Fmd?`xC5Ga_ZL zgN;TAfG;o6m+7;xm7l`*1e($xXKy9{6~S5hB7H6be}abH$${BjWDD6!pQkU-zy1!) z1ne3RKLp6@12_F3B?2CUTHES<-Ui{qBO}D$7ev(JA@T&$>>~Tg!{h*Ygd8M~l0)P$ zd5jz(kJDG^tMoN`f}W(O=-=pR`Z|4sp4mm76zb;W7?pn|#)sP2Zv) zL*<;i8M^>2kS1JO-W4MC|6g_Uf1@J)=Su9C7VH17>*nMQ&JfSgvpscl^45TL^N1>Z zKt6&pDmhO+r03|{UF2hOfxbgOS z=y|A{!$g0w!2Q3eo&OKz_!H!x08u~vsZuxj6-a-(l=RZ7de7c6go%Z{Wf=4Z{fLg~ zLT}-7PzAr0j{aTkJ3=pG;i1ir@2sG-LFcgPo@kbWyyNdFxJxH&E^Dl5+~ zb(CgQz{Olfetu42Mp-a(@ zfIif)*hQR^4NY+`66$e)L;ZVuE~uRifmU!crhc9L@IVl1sbI6-R&a-q2x$6|@Z33{ z6p{0CrV{Fo%oyehri3YF%9wJ-!Bo&6=s)O>^q=%6`ZN87{t9(R{v+Hx!gr9L`v`ZA z+&6HdiQ`$GXA%jF#ty!AouevZGok0cgc`Efw%sgWqMKmE`@qOYlPOohIF z%!mIG@kBQ4_JCXNPtKv}z_P$j^(Xy9v7K%8p~a9=we+XY`Xkmg!2nke;v9DW5q~Iq zG*j-g)W|W`F|#1WXJ&R_)XB{5z?eG_HN>J(4dnwP-g7foVM!O`X6idI>A>P>gvhXr z7m$u=WSW@f4$O4mm<}A<%@|Q0;{!v<>cAqnZvv*h5!Gz&t+)27f>%xpC%T*9lD^FY zcY48XeJcn|h*>ny9BDAe5->%WLqbPeOV$?k8?}sCE&z)va-YVmWLEXAcy$Ms_grN$ zYq`~}W7c(u5Kf9(?B!d3^U)_f#bQUm2`9+oR@$r))Fr-Lv+T?%$9)|Y(>MF z&JL`m*&R56^9D*_5y_f4^|zMP35?*UXj)_eL&||4oLzw?qiJY1nhOEuM)(m_H`xu* z%pr(j9w$f1GvrzFJb9N9Gl@(FU`P&=#}qIl;Ac&vn9IRdOTkVn;HOMiG4=3crbecj znaB9xM@_BpgQhUEkZEUbX7(^gnDa667-LL+%;Xq0W@*gEm>n^@WA?^86!UP*(=ji^ zoQ`=T=FOP5Vh6`&$Bv41#9kRYDRxGzJJuWPkG(pU#;%CHCU$M?`q=AYua7+%`;7>R z7*VW9B9e;aBCSX#GKkVd3q;FA%SBYQLbP7gDcUJ|SoE~$S<&;NzlvTJoe-T8ofdr{ zIxqTA^s(q$u}rKGE5&N@AaRm7S*#W7#0GJi*eos*j}(sm8OP`mX zkiH{*SNfjx1L;NSchVoEKT3a^a#BvQOn!`2=~L ze71b9+%0dAvvR+Dk$ky)jeMQ_X8C6MR{8z%2jzR^`{f7Z2jx%6Uyz@YpH{>w;uR*v zU`4j#az%-vOi`g2tEf`6D>@YG6dM$q6!$B3EA}Y%DfTN4C=MzPDIQZiu6RvxQt>y% z>xwgqvx;+ycNFg_K2Ut9xEL>vH^t}0Um5R;_r`a`Z;tPbzbF31_!IHx;y;Z4BL2(x zui}4IDwS&GAZ3y=S*ca(lm=y*GFzFe%vTmFi|BRR^6%EqS~hFQf*h=t=g%2MD?iZubKNyt3Ou%UHz&0bM=?% zuM#8)`hk|(qK9P7K zX=2jMq&Z2hr1~UJQe)D_q}!6VC*7O0E9rrxeM$S14kR5+dS4^bC^afgf<~v&YmAz~ zntV-_X1r#iW|C&I#;K{*OxM(DW@+YVf|{^qp{8B4M6*=0OhYv*HLEpiHS0CIHD@)y zCdVabC67&>lT4GZPwr0MlYA`s_2e_jXOqt-Urhck1*d3IQc`kK94XF}`6|GEl)F>jNO>>iLdqv8pQU_}axvu>twbBIRcRBniP|CB9BqxZRvXrKXjf`i zX|L7ZpuJ1GRokiU*6z_hrai8GPWyuP#nj|fQ)*%AsMN`+GgH~rrc`h0j?~?$hf<$P zeJS;w)C;;nx)fcxZh~%-ZnDm)yGqxh3+NW;Lb^8HBHd!$YTX*$I^71{M%@j%n{>D6 z_UoS1y{vm*_pM%}m*|u9dc9F^(p&VG>4)hv^jZ2GeWiY`zD>VO-=SZrU!}iKe_a2G z{%1p~A=}_E)ENARRzuJbHY_x>8qyfvDP@o03f?lf{&7vYUpOhMGp0Mw`Z%N=y}|v8F21c$43>-gLWZtEtn} zZQ5bF-?ZDb$F$G1-*nEbG-sQO&6Val^DMK+yuf^odA0dw^X=x%<~z-u=5F&2^F8JR z=A-6g=4Z^$nopU}n9rKencp{`H-BWlVE(~kv<$Y4v6Ne8TAD1amY^kUS!ijubXZne zR#~pKthKDS++x{cx!3ZPt3mNS;KmUEVOEEg=FSU$6SVYz7e+N!cq6_b z*0t94*6Xa-TW_@9Y`xWbn{}u4KI{F~-PS$Ueb)Wf1J;AqL)HuFDd`pI;qe*gdg literal 0 HcmV?d00001 diff --git a/iOS/Kordant.xcodeproj/xcuserdata/mike.xcuserdatad/xcschemes/xcschememanagement.plist b/iOS/Kordant.xcodeproj/xcuserdata/mike.xcuserdatad/xcschemes/xcschememanagement.plist new file mode 100644 index 0000000..dcf613c --- /dev/null +++ b/iOS/Kordant.xcodeproj/xcuserdata/mike.xcuserdatad/xcschemes/xcschememanagement.plist @@ -0,0 +1,14 @@ + + + + + SchemeUserState + + ShieldAI.xcscheme_^#shared#^_ + + orderHint + 0 + + + + diff --git a/iOS/KordantTests/ShieldAITests.swift b/iOS/KordantTests/ShieldAITests.swift new file mode 100644 index 0000000..9f99188 --- /dev/null +++ b/iOS/KordantTests/ShieldAITests.swift @@ -0,0 +1,1914 @@ +import Testing +@testable import Kordant +import SwiftUI + +// MARK: - Mocks + +final class MockKeychainService: KeychainServiceProtocol { + private var storage: [String: Data] = [:] + + func store(key: String, value: Data) throws { + storage[key] = value + } + + func retrieve(key: String) throws -> Data? { + storage[key] + } + + func delete(key: String) throws { + storage.removeValue(forKey: key) + } + + func clearAll() throws { + storage.removeAll() + } +} + +final class MockAuthAPIClient: AuthAPIClientProtocol { + var shouldSucceed = true + + func login(email: String, password: String) async throws -> AuthTokenResponse { + if shouldSucceed { + return AuthTokenResponse( + accessToken: "mock-token", + refreshToken: "mock-refresh", + user: User(id: "1", name: "Test", email: email) + ) + } + throw APIError.notImplemented + } + + func signup(name: String, email: String, password: String) async throws -> AuthTokenResponse { + if shouldSucceed { + return AuthTokenResponse( + accessToken: "mock-token", + refreshToken: "mock-refresh", + user: User(id: "1", name: name, email: email) + ) + } + throw APIError.notImplemented + } + + func resetPassword(email: String) async throws { + if !shouldSucceed { + throw APIError.notImplemented + } + } +} + +// MARK: - Password Strength Tests + +struct PasswordStrengthTests { + @Test("Short password with no criteria is weak") + func shortPassword() { + #expect(PasswordStrengthCalculator.strength(of: "abc") == .weak) + } + + @Test("Password meeting one criterion is weak") + func oneCriterion() { + #expect(PasswordStrengthCalculator.strength(of: "abcdefgh") == .weak) + } + + @Test("Password with length and uppercase is fair") + func lengthAndUppercase() { + #expect(PasswordStrengthCalculator.strength(of: "Abcdefgh") == .fair) + } + + @Test("Password with length, uppercase, and digit is good") + func threeCriteria() { + #expect(PasswordStrengthCalculator.strength(of: "Abcdefg1") == .good) + } + + @Test("Password meeting all criteria is strong") + func allCriteria() { + #expect(PasswordStrengthCalculator.strength(of: "Abcdefg1!") == .strong) + } + + @Test("Empty password is weak") + func emptyPassword() { + #expect(PasswordStrengthCalculator.strength(of: "") == .weak) + } +} + +// MARK: - Keychain Service Tests + +struct KeychainServiceTests { + @Test("MockKeychainService stores and retrieves data") + func storeAndRetrieve() throws { + let keychain = MockKeychainService() + try keychain.store(key: "test", value: Data("hello".utf8)) + let result = try keychain.retrieve(key: "test") + #expect(result == Data("hello".utf8)) + } + + @Test("MockKeychainService returns nil for missing key") + func missingKey() throws { + let keychain = MockKeychainService() + let result = try keychain.retrieve(key: "nonexistent") + #expect(result == nil) + } + + @Test("MockKeychainService overwrites existing value") + func overwriteValue() throws { + let keychain = MockKeychainService() + try keychain.store(key: "key", value: Data("first".utf8)) + try keychain.store(key: "key", value: Data("second".utf8)) + let result = try keychain.retrieve(key: "key") + #expect(result == Data("second".utf8)) + } + + @Test("MockKeychainService deletes value") + func deleteValue() throws { + let keychain = MockKeychainService() + try keychain.store(key: "key", value: Data("value".utf8)) + try keychain.delete(key: "key") + let result = try keychain.retrieve(key: "key") + #expect(result == nil) + } + + @Test("MockKeychainService clears all values") + func clearAll() throws { + let keychain = MockKeychainService() + try keychain.store(key: "a", value: Data("1".utf8)) + try keychain.store(key: "b", value: Data("2".utf8)) + try keychain.clearAll() + #expect(try keychain.retrieve(key: "a") == nil) + #expect(try keychain.retrieve(key: "b") == nil) + } +} + +// MARK: - Auth Service Tests + +struct AuthServiceTests { + @MainActor + private func makeService(apiClient: MockAuthAPIClient = MockAuthAPIClient()) -> AuthService { + AuthService( + keychain: MockKeychainService(), + apiClient: apiClient + ) + } + + @Test("AuthService starts unauthenticated") + @MainActor + func initialState() { + let service = makeService() + #expect(service.state == .unauthenticated) + #expect(service.currentUser == nil) + #expect(!service.isBiometricEnabled) + #expect(!service.hasCompletedOnboarding) + } + + @Test("AuthService login succeeds and updates state") + @MainActor + func loginSuccess() async { + let client = MockAuthAPIClient() + client.shouldSucceed = true + let service = makeService(apiClient: client) + + await service.login(email: "test@example.com", password: "password123") + + #expect(service.state == .authenticated) + #expect(service.currentUser?.email == "test@example.com") + #expect(service.currentUser?.name == "Test") + } + + @Test("AuthService login failure sets error") + @MainActor + func loginFailure() async { + let client = MockAuthAPIClient() + client.shouldSucceed = false + let service = makeService(apiClient: client) + + await service.login(email: "test@example.com", password: "wrong") + + #expect(service.state == .unauthenticated) + #expect(service.error != nil) + } + + @Test("AuthService signup succeeds and updates state") + @MainActor + func signupSuccess() async { + let client = MockAuthAPIClient() + client.shouldSucceed = true + let service = makeService(apiClient: client) + + await service.signup(name: "Test User", email: "test@example.com", password: "password123") + + #expect(service.state == .authenticated) + #expect(service.currentUser?.name == "Test User") + #expect(service.currentUser?.email == "test@example.com") + } + + @Test("AuthService logout clears all state") + @MainActor + func logout() async { + let client = MockAuthAPIClient() + client.shouldSucceed = true + let service = makeService(apiClient: client) + + await service.login(email: "test@example.com", password: "password123") + service.completeOnboarding() + service.enableBiometric() + service.logout() + + #expect(service.state == .unauthenticated) + #expect(service.currentUser == nil) + #expect(!service.isBiometricEnabled) + #expect(!service.hasCompletedOnboarding) + } + + @Test("AuthService shows biometric prompt after first login") + @MainActor + func biometricPromptAfterLogin() async { + let client = MockAuthAPIClient() + client.shouldSucceed = true + let service = makeService(apiClient: client) + + await service.login(email: "test@example.com", password: "password123") + + #expect(service.showBiometricPrompt) + } + + @Test("AuthService does not show biometric prompt if already enabled") + @MainActor + func noBiometricPromptIfEnabled() async { + let keychain = MockKeychainService() + try? keychain.store(key: "useBiometric", value: Data("token".utf8)) + let client = MockAuthAPIClient() + client.shouldSucceed = true + let service = AuthService(keychain: keychain, apiClient: client) + + await service.login(email: "test@example.com", password: "password123") + + #expect(!service.showBiometricPrompt) + } + + @Test("AuthService enabling biometric sets flag and hides prompt") + @MainActor + func enableBiometric() async { + let client = MockAuthAPIClient() + client.shouldSucceed = true + let service = makeService(apiClient: client) + await service.login(email: "test@example.com", password: "password123") + + service.enableBiometric() + + #expect(service.isBiometricEnabled) + #expect(!service.showBiometricPrompt) + } + + @Test("AuthService onboarding state persists") + @MainActor + func onboardingCompletion() { + let service = makeService() + #expect(!service.hasCompletedOnboarding) + + service.completeOnboarding() + + #expect(service.hasCompletedOnboarding) + } + + @Test("AuthService resetPassword succeeds without error") + @MainActor + func resetPasswordSuccess() async { + let client = MockAuthAPIClient() + client.shouldSucceed = true + let service = makeService(apiClient: client) + + await service.resetPassword(email: "test@example.com") + + #expect(service.error == nil) + } + + @Test("AuthService resetPassword failure sets error") + @MainActor + func resetPasswordFailure() async { + let client = MockAuthAPIClient() + client.shouldSucceed = false + let service = makeService(apiClient: client) + + await service.resetPassword(email: "test@example.com") + + #expect(service.error != nil) + } +} + +// MARK: - ThemeManager Tests + +struct ThemeManagerTests { + @MainActor + private func makeManager() -> ThemeManager { + ThemeManager(defaults: UserDefaults(suiteName: UUID().uuidString)!) + } + + @Test("ThemeManager starts with system mode by default") + @MainActor + func defaultThemeMode() { + let manager = makeManager() + #expect(manager.colorScheme == nil) + } + + @Test("ThemeManager switches to light mode") + @MainActor + func setLightMode() { + let manager = makeManager() + manager.setLight() + #expect(manager.colorScheme == .light) + } + + @Test("ThemeManager switches to dark mode") + @MainActor + func setDarkMode() { + let manager = makeManager() + manager.setDark() + #expect(manager.colorScheme == .dark) + } + + @Test("ThemeManager switches back to system mode") + @MainActor + func setSystemMode() { + let manager = makeManager() + manager.setLight() + #expect(manager.colorScheme == .light) + manager.setSystem() + #expect(manager.colorScheme == nil) + } +} + +// MARK: - Color Tests + +struct ColorTests { + @Test("Brand primary color is accessible") + func brandPrimaryColor() { + #expect(Color.brandPrimary != .clear) + } + + @Test("Brand accent color is accessible") + func brandAccentColor() { + #expect(Color.brandAccent != .clear) + } + + @Test("Semantic colors are accessible") + func semanticColors() { + #expect(Color.success != .clear) + #expect(Color.warning != .clear) + #expect(Color.error != .clear) + } + + @Test("Adaptive background colors are accessible") + func adaptiveColors() { + #expect(Color.bgPrimary != .clear) + #expect(Color.bgSecondary != .clear) + #expect(Color.bgTertiary != .clear) + } + + @Test("Text colors are accessible") + func textColors() { + #expect(Color.textPrimary != .clear) + #expect(Color.textSecondary != .clear) + #expect(Color.textTertiary != .clear) + } + + @Test("Border color is accessible") + func borderColor() { + #expect(Color.border != .clear) + } +} + +// MARK: - Route Tests + +struct RouteTests { + @Test("Route initializes from valid dashboard deep link") + func dashboardDeepLink() { + guard let url = URL(string: "kordant://dashboard") else { + Issue.record("Could not create URL") + return + } + let route = Route(deepLink: url) + #expect(route == .dashboard) + } + + @Test("Route initializes from valid alerts deep link") + func alertsDeepLink() { + guard let url = URL(string: "kordant://alerts") else { + Issue.record("Could not create URL") + return + } + let route = Route(deepLink: url) + #expect(route == .alerts) + } + + @Test("Route initializes from alert detail deep link") + func alertDetailDeepLink() { + guard let url = URL(string: "kordant://alerts/abc123") else { + Issue.record("Could not create URL") + return + } + let route = Route(deepLink: url) + #expect(route == .alertDetail(id: "abc123")) + } + + @Test("Route returns nil for invalid scheme") + func invalidScheme() { + guard let url = URL(string: "https://example.com") else { + Issue.record("Could not create URL") + return + } + let route = Route(deepLink: url) + #expect(route == nil) + } + + @Test("Route returns nil for unknown host") + func unknownHost() { + guard let url = URL(string: "kordant://unknown") else { + Issue.record("Could not create URL") + return + } + let route = Route(deepLink: url) + #expect(route == nil) + } +} + +// MARK: - AppRouter Tests + +struct AppRouterTests { + @Test("AppRouter starts with empty path") + @MainActor + func emptyPath() { + let router = AppRouter() + #expect(router.path.isEmpty) + } + + @Test("AppRouter navigates to a route") + @MainActor + func navigateToRoute() { + let router = AppRouter() + router.navigate(to: .dashboard) + #expect(!router.path.isEmpty) + } + + @Test("AppRouter pops to root clears path") + @MainActor + func popToRoot() { + let router = AppRouter() + router.navigate(to: .dashboard) + router.navigate(to: .settings) + router.popToRoot() + #expect(router.path.isEmpty) + } + + @Test("AppRouter pop removes last route") + @MainActor + func popRemovesLast() { + let router = AppRouter() + router.navigate(to: .dashboard) + router.navigate(to: .settings) + router.pop() + #expect(router.path.count == 1) + } +} + +// MARK: - Spacing Tests + +struct SpacingTests { + @Test("Spacing values match spec") + func spacingValues() { + #expect(Spacing.xs == 4) + #expect(Spacing.sm == 8) + #expect(Spacing.md == 16) + #expect(Spacing.lg == 24) + #expect(Spacing.xl == 32) + #expect(Spacing.xxl == 48) + } +} + +// MARK: - Component Tests + +struct ShieldButtonTests { + @Test("ShieldButtonStyle has all cases") + func buttonStyles() { + let styles: [ShieldButtonStyle] = [.primary, .secondary, .ghost, .danger] + #expect(styles.count == 4) + } + + @Test("ShieldButtonSize has all cases") + func buttonSizes() { + let sizes: [ShieldButtonSize] = [.small, .medium, .large] + #expect(sizes.count == 3) + } + + @Test("ShieldButton action fires on tap") + @MainActor + func buttonAction() { + var fired = false + let button = ShieldButton(title: "Test", action: { fired = true }) + button.action() + #expect(fired) + } +} + +struct ShieldBadgeTests { + @Test("ShieldBadgeVariant has all cases") + func badgeVariants() { + let variants: [ShieldBadgeVariant] = [.default, .success, .warning, .error, .info] + #expect(variants.count == 5) + } +} + +struct ShieldTextFieldTests { + @Test("ShieldTextField renders with label") + @MainActor + func textFieldLabel() { + let text = "test" + _ = ShieldTextField(label: "Username", text: .constant(text)) + #expect(text == "test") + } + + @Test("ShieldTextField accepts error message") + @MainActor + func textFieldError() { + let field = ShieldTextField( + label: "Email", + text: .constant("bad"), + errorMessage: "Invalid email" + ) + #expect(field.errorMessage == "Invalid email") + } +} + +struct ToastTests { + @Test("ToastData creates with defaults") + func toastDataDefaults() { + let toast = ToastData(message: "Hello") + #expect(toast.message == "Hello") + #expect(toast.variant == .info) + #expect(toast.duration == 3.5) + } + + @Test("ToastData creates with custom values") + func toastDataCustom() { + let toast = ToastData(message: "Error!", variant: .error, duration: 5.0) + #expect(toast.message == "Error!") + #expect(toast.variant == .error) + #expect(toast.duration == 5.0) + } + + @Test("ToastData instances are equatable by id") + func toastDataEquality() { + let toast1 = ToastData(message: "Test") + let toast2 = ToastData(message: "Test") + #expect(toast1 != toast2) + } + + @MainActor + @Test("ToastManager shows and dismisses toast") + func toastManagerShowDismiss() { + let manager = ToastManager.shared + let initialCount = manager.toasts.count + manager.showToast(message: "Test toast") + #expect(manager.toasts.count == initialCount + 1) + if let toast = manager.toasts.last { + manager.dismiss(toast) + #expect(manager.toasts.count == initialCount) + } + } +} + +struct ShieldAvatarTests { + @Test("ShieldAvatarSize has all cases") + func avatarSizes() { + let sizes: [ShieldAvatarSize] = [.small, .medium, .large] + #expect(sizes.count == 3) + #expect(ShieldAvatarSize.small.dimension == 32) + #expect(ShieldAvatarSize.medium.dimension == 44) + #expect(ShieldAvatarSize.large.dimension == 64) + } + + @Test("AvatarStatus has all cases") + func avatarStatus() { + let statuses: [AvatarStatus] = [.online, .away, .offline] + #expect(statuses.count == 3) + } +} + +struct ShieldProgressBarTests { + @Test("ShieldProgressBar clamps progress between 0 and 1") + func progressClamping() { + let over = ShieldProgressBar(progress: 1.5) + let under = ShieldProgressBar(progress: -0.5) + #expect(over.progress <= 1.0) + #expect(under.progress >= 0.0) + } +} + +struct ToastVariantTests { + @Test("ToastVariant has all cases") + func toastVariants() { + let variants: [ToastVariant] = [.success, .error, .warning, .info] + #expect(variants.count == 4) + } +} + +// MARK: - Plan Tests + +struct PlanTests { + @Test("Plan has all expected cases") + func planCases() { + let plans = Plan.allCases + #expect(plans.count == 3) + #expect(plans.contains(.basic)) + #expect(plans.contains(.plus)) + #expect(plans.contains(.premium)) + } + + @Test("Plus plan is recommended") + func recommendedPlan() { + #expect(Plan.basic.isRecommended == false) + #expect(Plan.plus.isRecommended == true) + #expect(Plan.premium.isRecommended == false) + } + + @Test("Each plan has pricing") + func planPricing() { + #expect(Plan.basic.monthlyPrice == "Free") + #expect(Plan.plus.monthlyPrice == "$12/mo") + #expect(Plan.premium.monthlyPrice == "$29/mo") + } + + @Test("Premium plan has most features") + func planFeatures() { + #expect(Plan.basic.features.count < Plan.plus.features.count) + #expect(Plan.plus.features.count < Plan.premium.features.count) + } +} + +// MARK: - PasswordStrength Tests + +struct PasswordStrengthColorTests { + @Test("PasswordStrength has correct label for each level") + func strengthLabels() { + #expect(PasswordStrength.weak.label == "Weak") + #expect(PasswordStrength.fair.label == "Fair") + #expect(PasswordStrength.good.label == "Good") + #expect(PasswordStrength.strong.label == "Strong") + } + + @Test("PasswordStrength levels are comparable") + func strengthComparison() { + #expect(PasswordStrength.weak < PasswordStrength.fair) + #expect(PasswordStrength.fair < PasswordStrength.good) + #expect(PasswordStrength.good < PasswordStrength.strong) + } +} + +// MARK: - Mock URL Protocol + +final class MockURLProtocol: URLProtocol { + static var requestHandler: ((URLRequest) throws -> (HTTPURLResponse, Data))? + + override class func canInit(with request: URLRequest) -> Bool { true } + override class func canonicalRequest(for request: URLRequest) -> URLRequest { request } + + override func startLoading() { + guard let handler = Self.requestHandler else { + fatalError("MockURLProtocol.requestHandler not set") + } + do { + let (response, data) = try handler(request) + client?.urlProtocol(self, didReceive: response, cacheStoragePolicy: .notAllowed) + client?.urlProtocol(self, didLoad: data) + client?.urlProtocolDidFinishLoading(self) + } catch { + client?.urlProtocol(self, didFailWithError: error) + } + } + + override func stopLoading() {} +} + +// MARK: - APIClient Tests + +struct APIClientTests { + private func makeSession() -> URLSession { + let config = URLSessionConfiguration.ephemeral + config.protocolClasses = [MockURLProtocol.self] + return URLSession(configuration: config) + } + + @Test("APIClient injects auth header correctly") + func authHeaderInjection() async throws { + let session = makeSession() + let client = APIClient(session: session) + client.authToken = "test-jwt-token" + + MockURLProtocol.requestHandler = { request in + let authHeader = request.value(forHTTPHeaderField: "Authorization") + #expect(authHeader == "Bearer test-jwt-token") + let response = HTTPURLResponse(url: request.url!, statusCode: 200, httpVersion: nil, headerFields: nil)! + let data = try JSONEncoder().encode(User(id: "1", name: "Test", email: "test@example.com")) + return (response, data) + } + + let _: User = try await client.request("/api/trpc/user.me", method: "GET") + } + + @Test("APIClient retries on server error") + func retryOnServerError() async throws { + let session = makeSession() + let config = APIConfig(maxRetries: 2) + let client = APIClient(config: config, session: session) + client.authToken = "test-token" + + var attemptCount = 0 + MockURLProtocol.requestHandler = { request in + attemptCount += 1 + if attemptCount < 2 { + let response = HTTPURLResponse(url: request.url!, statusCode: 500, httpVersion: nil, headerFields: nil)! + return (response, Data()) + } + let response = HTTPURLResponse(url: request.url!, statusCode: 200, httpVersion: nil, headerFields: nil)! + let data = try JSONEncoder().encode(["message": "ok"]) + return (response, data) + } + + let result: [String: String] = try await client.request("/test") + #expect(result["message"] == "ok") + #expect(attemptCount == 2) + } + + @Test("APIClient throws unauthorized on 401") + func unauthorizedError() async throws { + let session = makeSession() + let client = APIClient(session: session) + + MockURLProtocol.requestHandler = { request in + let response = HTTPURLResponse(url: request.url!, statusCode: 401, httpVersion: nil, headerFields: nil)! + return (response, Data()) + } + + await #expect(throws: APIError.unauthorized) { + let _: String = try await client.request("/test") + } + } + + @Test("APIClient sets content type headers") + func contentTypeHeaders() async throws { + let session = makeSession() + let client = APIClient(session: session) + + MockURLProtocol.requestHandler = { request in + #expect(request.value(forHTTPHeaderField: "Content-Type") == "application/json") + #expect(request.value(forHTTPHeaderField: "Accept") == "application/json") + let response = HTTPURLResponse(url: request.url!, statusCode: 200, httpVersion: nil, headerFields: nil)! + return (response, Data("\"ok\"".utf8)) + } + + let _: String = try await client.request("/test") + } +} + +// MARK: - TRPCBridge Tests + +struct TRPCBridgeTests { + @Test("TRPCBridge rawRequest returns correct data") + func rawRequestReturnsData() async throws { + let session = makeSession() + let client = APIClient(session: session) + let expectedJSON = "{\"0\":{\"result\":{\"data\":{\"id\":\"1\",\"name\":\"Test\",\"email\":\"test@example.com\"}}}}" + + MockURLProtocol.requestHandler = { request in + let response = HTTPURLResponse(url: request.url!, statusCode: 200, httpVersion: nil, headerFields: nil)! + let data = Data(expectedJSON.utf8) + return (response, data) + } + + let data = try await client.rawRequest("/api/trpc/user.me", method: "POST") + let json = try JSONSerialization.jsonObject(with: data) as? [String: Any] + #expect(json != nil) + let firstVal = json?["0"] as? [String: Any] + #expect(firstVal != nil) + let result = firstVal?["result"] as? [String: Any] + #expect(result != nil) + let dataVal = result?["data"] as? [String: Any] + #expect(dataVal != nil) + #expect(dataVal?["id"] as? String == "1") + } + + @Test("TRPCBridge callProcedure sends correct path") + func correctPath() async throws { + let session = makeSession() + let client = APIClient(session: session) + let bridge = TRPCBridge(client: client) + + MockURLProtocol.requestHandler = { request in + #expect(request.url?.absoluteString.contains("/api/trpc/user.me") == true) + #expect(request.httpMethod == "POST") + let response = HTTPURLResponse(url: request.url!, statusCode: 200, httpVersion: nil, headerFields: nil)! + let body: [String: Any] = ["0": ["result": ["data": ["id": "1", "name": "Test", "email": "test@example.com"]]]] + let data = try JSONSerialization.data(withJSONObject: body) + return (response, data) + } + + let user: User = try await bridge.callProcedure(path: "user.me") + #expect(user.id == "1") + #expect(user.name == "Test") + #expect(user.email == "test@example.com") + } + + @Test("TRPCBridge handles tRPC error format") + func trpcError() async throws { + let session = makeSession() + let client = APIClient(session: session) + let bridge = TRPCBridge(client: client) + + MockURLProtocol.requestHandler = { request in + let response = HTTPURLResponse(url: request.url!, statusCode: 200, httpVersion: nil, headerFields: nil)! + let body: [String: Any] = ["error": ["message": "User not found", "code": 404]] + let data = try JSONSerialization.data(withJSONObject: body) + return (response, data) + } + + await #expect(throws: APIError.tRPCError(code: 404, message: "User not found")) { + let _: User = try await bridge.callProcedure(path: "user.me") + } + } + + @Test("TRPCBridge userMe convenience method") + func userMeConvenience() async throws { + let session = makeSession() + let client = APIClient(session: session) + client.authToken = "test-token" + let bridge = TRPCBridge(client: client) + + MockURLProtocol.requestHandler = { request in + #expect(request.url?.absoluteString.hasSuffix("/api/trpc/user.me") == true) + let response = HTTPURLResponse(url: request.url!, statusCode: 200, httpVersion: nil, headerFields: nil)! + let body: [String: Any] = ["0": ["result": ["data": ["id": "1", "name": "Test", "email": "test@example.com"]]]] + let data = try JSONSerialization.data(withJSONObject: body) + return (response, data) + } + + let user = try await bridge.userMe() + #expect(user.id == "1") + } + + private func makeSession() -> URLSession { + let config = URLSessionConfiguration.ephemeral + config.protocolClasses = [MockURLProtocol.self] + return URLSession(configuration: config) + } +} + +// MARK: - CacheManager Tests + +struct CacheManagerTests { + @Test("CacheManager stores and retrieves values") + func storeAndRetrieve() { + let defaults = UserDefaults(suiteName: UUID().uuidString)! + let cache = CacheManager(defaults: defaults) + + cache.setCached(key: "user", value: User(id: "1", name: "Test", email: "test@example.com"), ttl: 60) + let cached: User? = cache.getCached(key: "user") + #expect(cached?.id == "1") + #expect(cached?.name == "Test") + } + + @Test("CacheManager returns nil for expired TTL") + func expiredTTL() { + let defaults = UserDefaults(suiteName: UUID().uuidString)! + let cache = CacheManager(defaults: defaults) + + cache.setCached(key: "item", value: "test-value", ttl: -1) + let cached: String? = cache.getCached(key: "item") + #expect(cached == nil) + } + + @Test("CacheManager returns nil for missing key") + func missingKey() { + let defaults = UserDefaults(suiteName: UUID().uuidString)! + let cache = CacheManager(defaults: defaults) + + let cached: String? = cache.getCached(key: "nonexistent") + #expect(cached == nil) + } + + @Test("CacheManager clearAll removes all entries") + func clearAll() { + let defaults = UserDefaults(suiteName: UUID().uuidString)! + let cache = CacheManager(defaults: defaults) + + cache.setCached(key: "a", value: "1", ttl: 60) + cache.setCached(key: "b", value: "2", ttl: 60) + cache.clearAll() + + let a: String? = cache.getCached(key: "a") + let b: String? = cache.getCached(key: "b") + #expect(a == nil) + #expect(b == nil) + } + + @Test("CacheManager remove clears single key") + func removeKey() { + let defaults = UserDefaults(suiteName: UUID().uuidString)! + let cache = CacheManager(defaults: defaults) + + cache.setCached(key: "keep", value: "value", ttl: 60) + cache.setCached(key: "remove", value: "value", ttl: 60) + cache.remove(key: "remove") + + let kept: String? = cache.getCached(key: "keep") + let removed: String? = cache.getCached(key: "remove") + #expect(kept == "value") + #expect(removed == nil) + } +} + +// MARK: - OfflineQueue Tests + +struct OfflineQueueTests { + @Test("OfflineQueue persists and loads queued requests") + func persistAndLoad() { + let defaults = UserDefaults(suiteName: UUID().uuidString)! + let client = MockAPIClient() + let queue = OfflineQueue(client: client, defaults: defaults) + + let request = QueuedRequest(endpoint: "/test", method: "POST", body: Data("\"hello\"".utf8)) + queue.addToQueue(request) + + #expect(queue.pendingCount() == 1) + + let queue2 = OfflineQueue(client: client, defaults: defaults) + #expect(queue2.pendingCount() == 1) + } + + @Test("OfflineQueue processes queued requests successfully") + func processQueueSuccess() async { + let defaults = UserDefaults(suiteName: UUID().uuidString)! + let client = MockAPIClient() + client.shouldSucceed = true + let queue = OfflineQueue(client: client, defaults: defaults) + + let request = QueuedRequest(endpoint: "/test", method: "POST") + queue.addToQueue(request) + + await queue.processQueue() + #expect(queue.pendingCount() == 0) + } + + @Test("OfflineQueue retries failed requests up to maxRetries") + func processQueueRetries() async { + let defaults = UserDefaults(suiteName: UUID().uuidString)! + let client = MockAPIClient() + client.shouldSucceed = false + let queue = OfflineQueue(client: client, defaults: defaults) + + let request = QueuedRequest(endpoint: "/test", method: "POST") + queue.addToQueue(request) + + await queue.processQueue() + #expect(queue.pendingCount() == 1) + + var loaded = loadFromDefaults(defaults) + #expect(loaded?.first?.retryCount == 1) + } + + @Test("OfflineQueue marks failed after max retries") + func maxRetriesExhausted() async { + let defaults = UserDefaults(suiteName: UUID().uuidString)! + let client = MockAPIClient() + client.shouldSucceed = false + let queue = OfflineQueue(client: client, defaults: defaults) + + var request = QueuedRequest(endpoint: "/test", method: "POST") + request.retryCount = 3 + queue.addToQueue(request) + + await queue.processQueue() + #expect(queue.pendingCount() == 0) + } + + @Test("OfflineQueue clearQueue removes all") + func clearQueue() { + let defaults = UserDefaults(suiteName: UUID().uuidString)! + let client = MockAPIClient() + let queue = OfflineQueue(client: client, defaults: defaults) + + queue.addToQueue(QueuedRequest(endpoint: "/test", method: "POST")) + queue.addToQueue(QueuedRequest(endpoint: "/test2", method: "GET")) + queue.clearQueue() + #expect(queue.pendingCount() == 0) + } + + private func loadFromDefaults(_ defaults: UserDefaults) -> [QueuedRequest]? { + guard let data = defaults.data(forKey: "kordant.offlineQueue") else { return nil } + return try? JSONDecoder().decode([QueuedRequest].self, from: data) + } +} + +final class MockAPIClient: APIClientProtocol { + var shouldSucceed = true + var authToken: String? + let config: APIConfig = .shared + + func request(_ endpoint: String, method: String, body: Data?) async throws -> T { + let data = try await rawRequest(endpoint, method: method, body: body) + return try JSONDecoder().decode(T.self, from: data) + } + + func rawRequest(_ endpoint: String, method: String, body: Data?) async throws -> Data { + if shouldSucceed { + return try JSONSerialization.data(withJSONObject: ["message": "ok"]) + } + throw APIError.serverError(statusCode: 500) + } +} + +// MARK: - NetworkMonitor Tests + +struct NetworkMonitorTests { + @Test("NetworkMonitor starts connected by default") + func initialState() { + let monitor = NetworkMonitor() + #expect(monitor.isConnected == true) + monitor.stopMonitoring() + } +} + +// MARK: - Model Tests + +struct ModelTests { + @Test("User conforms to Codable") + func userCodable() throws { + let user = User(id: "1", name: "Test", email: "t@t.com") + let data = try JSONEncoder().encode(user) + let decoded = try JSONDecoder().decode(User.self, from: data) + #expect(decoded == user) + } + + @Test("User conforms to Identifiable") + func userIdentifiable() { + let user = User(id: "42", name: "A", email: "a@b.com") + #expect(user.id == "42") + } + + @Test("SubscriptionTier enum has all cases") + func subscriptionTierCases() { + let tiers = SubscriptionTier.allCases + #expect(tiers.count == 4) + #expect(tiers.contains(.free)) + #expect(tiers.contains(.basic)) + #expect(tiers.contains(.premium)) + #expect(tiers.contains(.enterprise)) + } + + @Test("Alert isCritical computed property") + func alertIsCritical() { + let critical = Alert(id: "1", userId: "1", type: .breach, severity: .critical, title: "!", message: "!", read: false, createdAt: nil) + let low = Alert(id: "2", userId: "1", type: .exposure, severity: .low, title: "?", message: "?", read: false, createdAt: nil) + #expect(critical.isCritical == true) + #expect(low.isCritical == false) + } + + @Test("AlertSeverity raw values") + func alertSeverityValues() { + #expect(AlertSeverity.low.rawValue == "low") + #expect(AlertSeverity.critical.rawValue == "critical") + } + + @Test("Subscription tier raw values") + func subscriptionTierRawValues() { + #expect(SubscriptionTier.free.rawValue == "free") + #expect(SubscriptionTier.enterprise.rawValue == "enterprise") + } + + @Test("Exposure source enum cases") + func exposureSourceCases() { + let sources: [ExposureSource] = [.darkWeb, .dataBreach, .socialMedia, .publicRecord, .brokerSite] + #expect(sources.count == 5) + } + + @Test("WatchlistItem has CodingKeys") + func watchlistItemCodingKeys() { + let item = WatchlistItem(id: "1", userId: "1", term: "test", type: .email, status: "active", createdAt: nil) + #expect(item.term == "test") + #expect(item.type == .email) + } +} + +// MARK: - Mock TRPCalling + +final class MockTRPCalling: TRPCalling { + var shouldSucceed = true + var stubbedAlerts: [Kordant.Alert] = [] + var stubbedExposures: [Exposure] = [] + var stubbedWatchlist: [WatchlistItem] = [] + var stubbedEnrollments: [VoiceEnrollment] = [] + var stubbedAnalyses: [VoiceAnalysis] = [] + var stubbedRules: [SpamRule] = [] + var stubbedProperties: [PropertyWatchlistItem] = [] + var stubbedRemovalRequests: [RemovalRequest] = [] + var stubbedBrokerListings: [BrokerListing] = [] + var stubbedCorrelationGroups: [CorrelationGroup] = [] + var stubbedUser = User(id: "1", name: "Test User", email: "test@kordant.ai") + var stubbedSubscription = Subscription(id: "1", userId: "1", tier: .premium, status: "active", currentPeriodStart: nil, currentPeriodEnd: nil, stripeCustomerId: nil, stripeSubscriptionId: nil) + var addedWatchlistItem: WatchlistItem? + var addedProperty: PropertyWatchlistItem? + var createdRule: SpamRule? + var startedRemoval: RemovalRequest? + + func callProcedure(path: String, input: (any Encodable)?) async throws -> T { + throw APIError.notImplemented + } + + func userMe() async throws -> User { + if shouldSucceed { return stubbedUser } + throw APIError.notImplemented + } + + func getSubscription() async throws -> Subscription { + if shouldSucceed { return stubbedSubscription } + throw APIError.notImplemented + } + + func getWatchlist() async throws -> [WatchlistItem] { + if shouldSucceed { return stubbedWatchlist } + throw APIError.notImplemented + } + + func getExposures() async throws -> [Exposure] { + if shouldSucceed { return stubbedExposures } + throw APIError.notImplemented + } + + func getAlerts() async throws -> [Kordant.Alert] { + if shouldSucceed { return stubbedAlerts } + throw APIError.notImplemented + } + + func getVoiceEnrollments() async throws -> [VoiceEnrollment] { + if shouldSucceed { return stubbedEnrollments } + throw APIError.notImplemented + } + + func getVoiceAnalyses() async throws -> [VoiceAnalysis] { + if shouldSucceed { return stubbedAnalyses } + throw APIError.notImplemented + } + + func getSpamRules() async throws -> [SpamRule] { + if shouldSucceed { return stubbedRules } + throw APIError.notImplemented + } + + func getPropertyWatchlist() async throws -> [PropertyWatchlistItem] { + if shouldSucceed { return stubbedProperties } + throw APIError.notImplemented + } + + func getRemovalRequests() async throws -> [RemovalRequest] { + if shouldSucceed { return stubbedRemovalRequests } + throw APIError.notImplemented + } + + func getBrokerListings() async throws -> [BrokerListing] { + if shouldSucceed { return stubbedBrokerListings } + throw APIError.notImplemented + } + + func getNormalizedAlerts() async throws -> [NormalizedAlert] { + if shouldSucceed { return [] } + throw APIError.notImplemented + } + + func getCorrelationGroups() async throws -> [CorrelationGroup] { + if shouldSucceed { return stubbedCorrelationGroups } + throw APIError.notImplemented + } + + func getSecurityReports() async throws -> [SecurityReport] { + if shouldSucceed { return [] } + throw APIError.notImplemented + } + + func addWatchlistItem(term: String, type: WatchlistItemType) async throws -> WatchlistItem { + if shouldSucceed { + let item = WatchlistItem(id: "new-1", userId: "1", term: term, type: type, status: "active", createdAt: nil) + addedWatchlistItem = item + return item + } + throw APIError.notImplemented + } + + func deleteWatchlistItem(id: String) async throws { + if !shouldSucceed { throw APIError.notImplemented } + } + + func scanForExposures() async throws -> [Exposure] { + if shouldSucceed { return stubbedExposures } + throw APIError.notImplemented + } + + func deleteVoiceEnrollment(id: String) async throws { + if !shouldSucceed { throw APIError.notImplemented } + } + + func createSpamRule(pattern: String, action: SpamRuleAction, priority: Int, enabled: Bool) async throws -> SpamRule { + if shouldSucceed { + let rule = SpamRule(id: "rule-1", userId: "1", pattern: pattern, action: action, priority: priority, enabled: enabled, createdAt: nil) + createdRule = rule + return rule + } + throw APIError.notImplemented + } + + func updateSpamRule(id: String, enabled: Bool) async throws -> SpamRule { + if shouldSucceed { + return SpamRule(id: id, userId: "1", pattern: "test", action: .block, priority: 1, enabled: enabled, createdAt: nil) + } + throw APIError.notImplemented + } + + func deleteSpamRule(id: String) async throws { + if !shouldSucceed { throw APIError.notImplemented } + } + + func addProperty(address: String, city: String, state: String, zipCode: String) async throws -> PropertyWatchlistItem { + if shouldSucceed { + let property = PropertyWatchlistItem(id: "prop-1", userId: "1", propertyType: "residential", address: address, city: city, state: state, zipCode: zipCode, status: "active", createdAt: nil) + addedProperty = property + return property + } + throw APIError.notImplemented + } + + func deleteProperty(id: String) async throws { + if !shouldSucceed { throw APIError.notImplemented } + } + + func startRemoval(exposureId: String, notes: String?) async throws -> RemovalRequest { + if shouldSucceed { + let request = RemovalRequest(id: "rem-1", userId: "1", exposureId: exposureId, status: .pending, requestedAt: nil, completedAt: nil, notes: notes) + startedRemoval = request + return request + } + throw APIError.notImplemented + } + + func checkPhoneNumber(_ number: String) async throws -> SpamCheckResult { + if shouldSucceed { + return SpamCheckResult(phone: number, isSpam: true, confidence: 0.85, category: "telemarketer", reportCount: 42) + } + throw APIError.notImplemented + } + + func resolveAlert(id: String) async throws { + if !shouldSucceed { throw APIError.notImplemented } + } + + func reportFalsePositive(id: String) async throws { + if !shouldSucceed { throw APIError.notImplemented } + } + + func updateNotificationPreferences(enabled: Bool) async throws { + if !shouldSucceed { throw APIError.notImplemented } + } + + func updateProfile(name: String, email: String) async throws -> User { + if shouldSucceed { + stubbedUser = User(id: "1", name: name, email: email) + return stubbedUser + } + throw APIError.notImplemented + } + + func registerDevice(token: String) async throws { + if !shouldSucceed { throw APIError.notImplemented } + } + + func createVoiceEnrollment(audioData: Data) async throws -> VoiceEnrollment { + if shouldSucceed { + return VoiceEnrollment(id: "new-enrollment", userId: "1", voiceSampleCount: 1, status: .pending, createdAt: Date()) + } + throw APIError.notImplemented + } +} + +// MARK: - ViewModel Tests + +@MainActor +struct DashboardViewModelTests { + @Test("DashboardViewModel loads alerts, exposures, and watchlist") + func loadDashboard() async { + let mock = MockTRPCalling() + mock.stubbedAlerts = [ + Alert(id: "1", userId: "1", type: .breach, severity: .critical, title: "Critical", message: "!", read: false, createdAt: Date()), + Alert(id: "2", userId: "1", type: .exposure, severity: .low, title: "Low", message: "?", read: true, createdAt: Date().addingTimeInterval(-3600)) + ] + mock.stubbedExposures = [ + Exposure(id: "1", userId: "1", source: .darkWeb, dataType: "email", exposedData: "a@b.com", severity: "high", discoveredAt: nil, status: .new), + Exposure(id: "2", userId: "1", source: .dataBreach, dataType: "password", exposedData: nil, severity: "medium", discoveredAt: nil, status: .remediated) + ] + mock.stubbedWatchlist = [ + WatchlistItem(id: "1", userId: "1", term: "test@email.com", type: .email, status: "active", createdAt: nil) + ] + + let vm = DashboardViewModel(api: mock) + await vm.loadDashboard() + + #expect(vm.alerts.count == 2) + #expect(vm.exposures.count == 2) + #expect(vm.watchlistItems.count == 1) + #expect(vm.isLoading == false) + #expect(vm.error == nil) + #expect(vm.unresolvedExposures == 1) + #expect(vm.activeWatchlistCount == 1) + } + + @Test("DashboardViewModel handles errors gracefully") + func loadDashboardError() async { + let mock = MockTRPCalling() + mock.shouldSucceed = false + + let vm = DashboardViewModel(api: mock) + await vm.loadDashboard() + + #expect(vm.alerts.isEmpty) + #expect(vm.error != nil) + #expect(vm.isLoading == false) + } + + @Test("DashboardViewModel threatScore with no issues") + func threatScoreNoIssues() async { + let mock = MockTRPCalling() + mock.stubbedAlerts = [] + mock.stubbedExposures = [] + + let vm = DashboardViewModel(api: mock) + await vm.loadDashboard() + + #expect(vm.threatScore == 0) + } + + @Test("DashboardViewModel threatScore with critical alerts") + func threatScoreCritical() async { + let mock = MockTRPCalling() + mock.stubbedAlerts = [ + Alert(id: "1", userId: "1", type: .breach, severity: .critical, title: "!", message: "!", read: false, createdAt: nil) + ] + mock.stubbedExposures = [ + Exposure(id: "1", userId: "1", source: .darkWeb, dataType: "email", exposedData: nil, severity: "high", discoveredAt: nil, status: .new) + ] + + let vm = DashboardViewModel(api: mock) + await vm.loadDashboard() + + #expect(vm.threatScore > 0) + } + + @Test("DashboardViewModel recentAlerts returns top 5") + func recentAlertsLimit() async { + let mock = MockTRPCalling() + mock.stubbedAlerts = (1...10).map { i in + Alert(id: "\(i)", userId: "1", type: .exposure, severity: .low, title: "Alert \(i)", message: "", read: false, createdAt: Date().addingTimeInterval(-Double(i) * 60)) + } + + let vm = DashboardViewModel(api: mock) + await vm.loadDashboard() + + #expect(vm.recentAlerts.count == 5) + } +} + +@MainActor +struct DarkWatchViewModelTests { + @Test("DarkWatchViewModel loads watchlist and exposures") + func loadData() async { + let mock = MockTRPCalling() + mock.stubbedWatchlist = [ + WatchlistItem(id: "1", userId: "1", term: "test@email.com", type: .email, status: "active", createdAt: nil) + ] + mock.stubbedExposures = [ + Exposure(id: "1", userId: "1", source: .darkWeb, dataType: "ssn", exposedData: nil, severity: "critical", discoveredAt: nil, status: .new) + ] + + let vm = DarkWatchViewModel(api: mock) + await vm.loadData() + + #expect(vm.watchlistItems.count == 1) + #expect(vm.exposures.count == 1) + #expect(vm.isLoading == false) + } + + @Test("DarkWatchViewModel adds watchlist item") + func addWatchlistItem() async { + let mock = MockTRPCalling() + let vm = DarkWatchViewModel(api: mock) + await vm.addWatchlistItem(term: "john@test.com", type: .email) + + #expect(vm.watchlistItems.count == 1) + #expect(vm.watchlistItems[0].term == "john@test.com") + #expect(vm.watchlistItems[0].type == .email) + } + + @Test("DarkWatchViewModel deletes watchlist item") + func deleteWatchlistItem() async { + let mock = MockTRPCalling() + mock.stubbedWatchlist = [ + WatchlistItem(id: "1", userId: "1", term: "test", type: .email, status: "active", createdAt: nil) + ] + let vm = DarkWatchViewModel(api: mock) + await vm.loadData() + #expect(vm.watchlistItems.count == 1) + + await vm.deleteWatchlistItem(id: "1") + #expect(vm.watchlistItems.isEmpty) + } + + @Test("DarkWatchViewModel scan for exposures") + func scanExposures() async { + let mock = MockTRPCalling() + mock.stubbedExposures = [ + Exposure(id: "1", userId: "1", source: .darkWeb, dataType: "email", exposedData: nil, severity: "high", discoveredAt: nil, status: .new) + ] + + let vm = DarkWatchViewModel(api: mock) + await vm.scanForExposures() + + #expect(vm.exposures.count == 1) + #expect(vm.isScanning == false) + } +} + +@MainActor +struct VoicePrintViewModelTests { + @Test("VoicePrintViewModel loads enrollments and analyses") + func loadData() async { + let mock = MockTRPCalling() + mock.stubbedEnrollments = [ + VoiceEnrollment(id: "1", userId: "1", voiceSampleCount: 3, status: .active, createdAt: nil) + ] + mock.stubbedAnalyses = [ + VoiceAnalysis(id: "1", userId: "1", enrollmentId: "1", result: "Match found", confidence: 0.92, processedAt: nil) + ] + + let vm = VoicePrintViewModel(api: mock) + await vm.loadData() + + #expect(vm.enrollments.count == 1) + #expect(vm.analyses.count == 1) + #expect(vm.isLoading == false) + } + + @Test("VoicePrintViewModel deletes enrollment") + func deleteEnrollment() async { + let mock = MockTRPCalling() + mock.stubbedEnrollments = [ + VoiceEnrollment(id: "1", userId: "1", voiceSampleCount: 3, status: .active, createdAt: nil) + ] + mock.stubbedAnalyses = [ + VoiceAnalysis(id: "1", userId: "1", enrollmentId: "1", result: "Match", confidence: 0.9, processedAt: nil) + ] + + let vm = VoicePrintViewModel(api: mock) + await vm.loadData() + #expect(vm.enrollments.count == 1) + + await vm.deleteEnrollment(id: "1") + #expect(vm.enrollments.isEmpty) + #expect(vm.analyses.isEmpty) + } +} + +@MainActor +struct SpamShieldViewModelTests { + @Test("SpamShieldViewModel loads rules") + func loadRules() async { + let mock = MockTRPCalling() + mock.stubbedRules = [ + SpamRule(id: "1", userId: "1", pattern: "+1234", action: .block, priority: 1, enabled: true, createdAt: nil), + SpamRule(id: "2", userId: "1", pattern: "spam", action: .flag, priority: 2, enabled: false, createdAt: nil) + ] + + let vm = SpamShieldViewModel(api: mock) + await vm.loadRules() + + #expect(vm.rules.count == 2) + #expect(vm.blockedCount == 1) + #expect(vm.flaggedCount == 0) + #expect(vm.allowedCount == 0) + } + + @Test("SpamShieldViewModel creates rule") + func createRule() async { + let mock = MockTRPCalling() + let vm = SpamShieldViewModel(api: mock) + + await vm.createRule(pattern: "555", action: .block, priority: 1) + #expect(vm.rules.count == 1) + #expect(vm.rules[0].pattern == "555") + #expect(vm.rules[0].action == .block) + } + + @Test("SpamShieldViewModel toggles rule") + func toggleRule() async { + let mock = MockTRPCalling() + mock.stubbedRules = [ + SpamRule(id: "1", userId: "1", pattern: "test", action: .block, priority: 1, enabled: true, createdAt: nil) + ] + let vm = SpamShieldViewModel(api: mock) + await vm.loadRules() + #expect(vm.rules[0].enabled == true) + + await vm.toggleRule(vm.rules[0]) + #expect(vm.rules[0].enabled == false) + } + + @Test("SpamShieldViewModel deletes rule") + func deleteRule() async { + let mock = MockTRPCalling() + mock.stubbedRules = [ + SpamRule(id: "1", userId: "1", pattern: "test", action: .block, priority: 1, enabled: true, createdAt: nil) + ] + let vm = SpamShieldViewModel(api: mock) + await vm.loadRules() + #expect(vm.rules.count == 1) + + await vm.deleteRule(id: "1") + #expect(vm.rules.isEmpty) + } + + @Test("SpamShieldViewModel checks phone number") + func checkNumber() async { + let mock = MockTRPCalling() + let vm = SpamShieldViewModel(api: mock) + vm.checkPhoneNumber = "+15551234567" + + await vm.checkNumber() + + #expect(vm.checkResult != nil) + #expect(vm.checkResult?.isSpam == true) + #expect(vm.checkResult?.confidence == 0.85) + #expect(vm.isCheckingNumber == false) + } +} + +@MainActor +struct HomeTitleViewModelTests { + @Test("HomeTitleViewModel loads properties") + func loadProperties() async { + let mock = MockTRPCalling() + mock.stubbedProperties = [ + PropertyWatchlistItem(id: "1", userId: "1", propertyType: "residential", address: "123 Main St", city: "Springfield", state: "IL", zipCode: "62701", status: "active", createdAt: nil) + ] + + let vm = HomeTitleViewModel(api: mock) + await vm.loadProperties() + + #expect(vm.properties.count == 1) + #expect(vm.properties[0].address == "123 Main St") + #expect(vm.isLoading == false) + } + + @Test("HomeTitleViewModel adds property") + func addProperty() async { + let mock = MockTRPCalling() + let vm = HomeTitleViewModel(api: mock) + + await vm.addProperty(address: "456 Oak Ave", city: "Portland", state: "OR", zipCode: "97201") + #expect(vm.properties.count == 1) + #expect(vm.properties[0].address == "456 Oak Ave") + } + + @Test("HomeTitleViewModel deletes property") + func deleteProperty() async { + let mock = MockTRPCalling() + mock.stubbedProperties = [ + PropertyWatchlistItem(id: "1", userId: "1", propertyType: "residential", address: "123 Main St", city: "Springfield", state: "IL", zipCode: "62701", status: "active", createdAt: nil) + ] + let vm = HomeTitleViewModel(api: mock) + await vm.loadProperties() + #expect(vm.properties.count == 1) + + await vm.deleteProperty(id: "1") + #expect(vm.properties.isEmpty) + } +} + +@MainActor +struct RemoveBrokersViewModelTests { + @Test("RemoveBrokersViewModel loads listings and requests") + func loadData() async { + let mock = MockTRPCalling() + mock.stubbedBrokerListings = [ + BrokerListing(id: "1", userId: "1", brokerName: "DataBrokerPro", url: "https://example.com", dataFound: true, status: .active, createdAt: nil) + ] + mock.stubbedRemovalRequests = [ + RemovalRequest(id: "1", userId: "1", exposureId: "exp-1", status: .inProgress, requestedAt: nil, completedAt: nil, notes: nil) + ] + + let vm = RemoveBrokersViewModel(api: mock) + await vm.loadData() + + #expect(vm.listings.count == 1) + #expect(vm.removalRequests.count == 1) + #expect(vm.isLoading == false) + } + + @Test("RemoveBrokersViewModel filters listings by search") + func filterListings() async { + let mock = MockTRPCalling() + mock.stubbedBrokerListings = [ + BrokerListing(id: "1", userId: "1", brokerName: "Acme Data", url: nil, dataFound: true, status: .active, createdAt: nil), + BrokerListing(id: "2", userId: "1", brokerName: "Global Info", url: nil, dataFound: false, status: .pending, createdAt: nil) + ] + + let vm = RemoveBrokersViewModel(api: mock) + await vm.loadData() + vm.searchQuery = "acme" + + #expect(vm.filteredListings.count == 1) + #expect(vm.filteredListings[0].brokerName == "Acme Data") + } + + @Test("RemoveBrokersViewModel starts removal") + func startRemoval() async { + let mock = MockTRPCalling() + let vm = RemoveBrokersViewModel(api: mock) + + await vm.startRemoval(exposureId: "exp-1", notes: "Urgent") + #expect(vm.removalRequests.count == 1) + #expect(vm.removalRequests[0].exposureId == "exp-1") + #expect(vm.removalRequests[0].notes == "Urgent") + } +} + +@MainActor +struct AlertDetailViewModelTests { + @Test("AlertDetailViewModel loads correlated alerts") + func loadCorrelated() async { + let mock = MockTRPCalling() + let alert = Alert(id: "alert-1", userId: "1", type: .breach, severity: .critical, title: "Test", message: "!", read: false, createdAt: nil) + mock.stubbedCorrelationGroups = [ + CorrelationGroup(id: "cg-1", userId: "1", name: "Group 1", alertIds: ["alert-1", "alert-2"], correlationScore: 0.9, createdAt: nil) + ] + mock.stubbedAlerts = [ + alert, + Alert(id: "alert-2", userId: "1", type: .exposure, severity: .medium, title: "Related", message: "?", read: false, createdAt: nil) + ] + + let vm = AlertDetailViewModel(alert: alert, api: mock) + await vm.loadCorrelatedAlerts() + + #expect(vm.correlationGroups.count == 1) + #expect(vm.correlatedAlerts.count == 1) + #expect(vm.correlatedAlerts[0].id == "alert-2") + } + + @Test("AlertDetailViewModel resolves alert") + func resolveAlert() async { + let mock = MockTRPCalling() + let alert = Alert(id: "alert-1", userId: "1", type: .breach, severity: .critical, title: "Test", message: "!", read: false, createdAt: nil) + + let vm = AlertDetailViewModel(alert: alert, api: mock) + await vm.resolveAlert() + + #expect(vm.isResolved == true) + } + + @Test("AlertDetailViewModel reports false positive") + func falsePositive() async { + let mock = MockTRPCalling() + let alert = Alert(id: "alert-1", userId: "1", type: .breach, severity: .critical, title: "Test", message: "!", read: false, createdAt: nil) + + let vm = AlertDetailViewModel(alert: alert, api: mock) + await vm.reportFalsePositive() + + #expect(vm.isResolved == true) + } +} + +@MainActor +struct SettingsViewModelTests { + @Test("SettingsViewModel loads user and subscription") + func loadSettings() async { + let mock = MockTRPCalling() + mock.stubbedUser = User(id: "1", name: "John Doe", email: "john@kordant.ai") + mock.stubbedSubscription = Subscription(id: "1", userId: "1", tier: .premium, status: "active", currentPeriodStart: nil, currentPeriodEnd: nil, stripeCustomerId: nil, stripeSubscriptionId: nil) + + let vm = SettingsViewModel(api: mock) + await vm.loadSettings() + + #expect(vm.user?.name == "John Doe") + #expect(vm.user?.email == "john@kordant.ai") + #expect(vm.subscription?.tier == .premium) + #expect(vm.name == "John Doe") + #expect(vm.email == "john@kordant.ai") + } + + @Test("SettingsViewModel updates profile") + func updateProfile() async { + let mock = MockTRPCalling() + mock.stubbedUser = User(id: "1", name: "Old Name", email: "old@kordant.ai", subscriptionTier: nil, createdAt: nil, updatedAt: nil) + + let vm = SettingsViewModel(api: mock) + await vm.loadSettings() + vm.name = "New Name" + vm.email = "new@kordant.ai" + + await vm.updateProfile() + #expect(vm.user?.name == "New Name") + #expect(vm.user?.email == "new@kordant.ai") + } + + @Test("SettingsViewModel error shows on API failure") + func loadSettingsError() async { + let mock = MockTRPCalling() + mock.shouldSucceed = false + + let vm = SettingsViewModel(api: mock) + await vm.loadSettings() + + #expect(vm.user == nil) + #expect(vm.error != nil) + } +} + +// MARK: - APIConfig Tests + +struct APIConfigTests { + @Test("APIConfig uses development in debug") + func debugEnvironment() { + let config = APIConfig() + #expect(config.environment == .development) + #expect(config.baseURL.absoluteString == "http://localhost:3000") + } + + @Test("APIConfig custom config") + func customConfig() { + let config = APIConfig(environment: .production, timeout: 60, maxRetries: 5) + #expect(config.environment == .production) + #expect(config.baseURL.absoluteString == "https://api.kordant.ai") + #expect(config.timeout == 60) + #expect(config.maxRetries == 5) + } + + @Test("APIConfig staging URL") + func stagingURL() { + let config = APIConfig(environment: .staging) + #expect(config.baseURL.absoluteString == "https://staging.kordant.ai") + } +} + +// MARK: - Push Notification Tests + +struct PushNotificationServiceTests { + @Test("NotificationPayload parses valid userInfo") + func validPayload() { + let userInfo: [AnyHashable: Any] = [ + "screen": "alerts", + "id": "alert-123", + "aps": ["alert": ["title": "Test Title", "body": "Test Body"]], + "image-url": "https://example.com/image.png" + ] + let payload = NotificationPayload(userInfo: userInfo) + #expect(payload?.screen == "alerts") + #expect(payload?.id == "alert-123") + #expect(payload?.title == "Test Title") + #expect(payload?.body == "Test Body") + #expect(payload?.imageURL?.absoluteString == "https://example.com/image.png") + } + + @Test("NotificationPayload returns nil without screen key") + func missingScreen() { + let userInfo: [AnyHashable: Any] = ["aps": [:]] + let payload = NotificationPayload(userInfo: userInfo) + #expect(payload == nil) + } + + @Test("NotificationPayload parses minimal payload") + func minimalPayload() { + let userInfo: [AnyHashable: Any] = ["screen": "dashboard"] + let payload = NotificationPayload(userInfo: userInfo) + #expect(payload?.screen == "dashboard") + #expect(payload?.id == nil) + #expect(payload?.imageURL == nil) + } + + @Test("Route initializes from notification payload") + func routeFromNotification() { + let payload: [AnyHashable: Any] = ["screen": "alerts", "id": "abc"] + let route = Route(notificationPayload: payload) + #expect(route == .alertDetail(id: "abc")) + } + + @Test("Route from notification payload returns dashboard for home screen") + func routeFromNotificationHome() { + let payload: [AnyHashable: Any] = ["screen": "home"] + let route = Route(notificationPayload: payload) + #expect(route == .dashboard) + } +} + +// MARK: - BiometricAuthService Tests + +@MainActor +struct BiometricAuthServiceTests { + @Test("BiometricAuthService uses mock keychain") + func usesMockKeychain() throws { + let keychain = MockKeychainService() + let service = BiometricAuthService(keychain: keychain) + #expect(!service.isEnabled) + } + + @Test("BiometricAuthService enable stores in keychain") + func enableBiometric() throws { + let keychain = MockKeychainService() + try keychain.store(key: "jwt", value: Data("token".utf8)) + let service = BiometricAuthService(keychain: keychain) + + try service.enable() + #expect(service.isEnabled) + } + + @Test("BiometricAuthService disable removes from keychain") + func disableBiometric() throws { + let keychain = MockKeychainService() + try keychain.store(key: "jwt", value: Data("token".utf8)) + let service = BiometricAuthService(keychain: keychain) + + try service.enable() + #expect(service.isEnabled) + + service.disable() + #expect(!service.isEnabled) + } + + @Test("BiometricAuthService biometryType returns unavailable on simulator") + func biometryTypeOnSimulator() { + let keychain = MockKeychainService() + let service = BiometricAuthService(keychain: keychain) + let type = service.biometryType + #expect(type != .faceID) // LAContext returns .none on simulator + } + + @Test("BiometricAuthService isAvailable returns false on simulator") + func isAvailableOnSimulator() { + let keychain = MockKeychainService() + let service = BiometricAuthService(keychain: keychain) + #expect(!service.isAvailable) // No biometric hardware on simulator + } +} + +// MARK: - CameraService Tests + +@MainActor +struct CameraServiceTests { + @Test("CameraService returns correct permission status defaults") + func permissionDefaults() { + let service = CameraService() + #expect(service.cameraPermission != .granted) // Not yet authorized + #expect(service.microphonePermission != .granted) + } + + @Test("CameraService usage descriptions are not empty") + func usageDescriptions() { + #expect(!CameraService.cameraUsageDescription().isEmpty) + #expect(!CameraService.microphoneUsageDescription().isEmpty) + } +} + +// MARK: - VoicePrintViewModel Tests (submit enrollment) + +@MainActor +struct VoicePrintSubmitTests { + @Test("VoicePrintViewModel submits enrollment") + func submitEnrollment() async { + let mock = MockTRPCalling() + mock.shouldSucceed = true + let vm = VoicePrintViewModel(api: mock) + + let tempURL = FileManager.default.temporaryDirectory.appendingPathComponent("test.wav") + try? Data("mock-audio".utf8).write(to: tempURL) + + await vm.submitEnrollment(audioURL: tempURL) + #expect(vm.enrollments.count == 1) + #expect(vm.enrollments[0].status == .pending) + #expect(vm.submitSuccess) + #expect(!vm.showingRecordingSheet) + } + + @Test("VoicePrintViewModel handles submission failure") + func submitEnrollmentFailure() async { + let mock = MockTRPCalling() + mock.shouldSucceed = false + let vm = VoicePrintViewModel(api: mock) + + let tempURL = FileManager.default.temporaryDirectory.appendingPathComponent("test.wav") + try? Data("mock-audio".utf8).write(to: tempURL) + + await vm.submitEnrollment(audioURL: tempURL) + #expect(vm.enrollments.isEmpty) + #expect(vm.error != nil) + #expect(!vm.isSubmitting) + } +} diff --git a/iOS/KordantUITests/ShieldAIUITests.swift b/iOS/KordantUITests/ShieldAIUITests.swift new file mode 100644 index 0000000..645f66b --- /dev/null +++ b/iOS/KordantUITests/ShieldAIUITests.swift @@ -0,0 +1,43 @@ +// +// KordantUITests.swift +// KordantUITests +// +// Created by Mike Freno on 5/25/26. +// + +import XCTest + +final class KordantUITests: XCTestCase { + + override func setUpWithError() throws { + // Put setup code here. This method is called before the invocation of each test method in the class. + + // In UI tests it is usually best to stop immediately when a failure occurs. + continueAfterFailure = false + + // In UI tests it’s important to set the initial state - such as interface orientation - required for your tests before they run. The setUp method is a good place to do this. + } + + override func tearDownWithError() throws { + // Put teardown code here. This method is called after the invocation of each test method in the class. + } + + @MainActor + func testExample() throws { + // UI tests must launch the application that they test. + let app = XCUIApplication() + app.launch() + + // Use XCTAssert and related functions to verify your tests produce the correct results. + // XCUIAutomation Documentation + // https://developer.apple.com/documentation/xcuiautomation + } + + @MainActor + func testLaunchPerformance() throws { + // This measures how long it takes to launch your application. + measure(metrics: [XCTApplicationLaunchMetric()]) { + XCUIApplication().launch() + } + } +} diff --git a/iOS/KordantUITests/ShieldAIUITestsLaunchTests.swift b/iOS/KordantUITests/ShieldAIUITestsLaunchTests.swift new file mode 100644 index 0000000..5c8e698 --- /dev/null +++ b/iOS/KordantUITests/ShieldAIUITestsLaunchTests.swift @@ -0,0 +1,35 @@ +// +// KordantUITestsLaunchTests.swift +// KordantUITests +// +// Created by Mike Freno on 5/25/26. +// + +import XCTest + +final class KordantUITestsLaunchTests: XCTestCase { + + override class var runsForEachTargetApplicationUIConfiguration: Bool { + true + } + + override func setUpWithError() throws { + continueAfterFailure = false + } + + @MainActor + func testLaunch() throws { + let app = XCUIApplication() + app.launch() + + // Insert steps here to perform after app launch but before taking a screenshot, + // such as logging into a test account or navigating somewhere in the app + // XCUIAutomation Documentation + // https://developer.apple.com/documentation/xcuiautomation + + let attachment = XCTAttachment(screenshot: app.screenshot()) + attachment.name = "Launch Screen" + attachment.lifetime = .keepAlways + add(attachment) + } +} diff --git a/tasks/rebrand-to-kordant/01-update-monorepo-foundation.md b/tasks/rebrand-to-kordant/01-update-monorepo-foundation.md deleted file mode 100644 index e89ce9f..0000000 --- a/tasks/rebrand-to-kordant/01-update-monorepo-foundation.md +++ /dev/null @@ -1,37 +0,0 @@ -# 01. Update Monorepo Foundation - -meta: - id: rebrand-to-kordant-01 - feature: rebrand-to-kordant - priority: P0 - depends_on: [] - tags: [infrastructure, configuration] - -objective: -- Update the root package name, workspace metadata, and environment configuration files from ShieldAI to Kordant. - -deliverables: -- Root package.json name changed from "shieldai" to "kordant" -- Turbo pipeline `build:ext` and `test:ext` script references updated -- Root .env.example updated (DB user, database name, DD_SERVICE) -- .env.prod.example updated (GITHUB_REPOSITORY_OWNER) -- .editorconfig: no changes needed (not brand-specific) - -steps: -1. Edit `package.json` — change `"name": "shieldai"` to `"name": "kordant"` -2. Edit `package.json` — update `"build:ext"` script: `@shieldai/browser-ext` → needs package scope update (will be done in task 03; for now just the script name stays but the scope resolves later) -3. Edit `.env.example` — update DATABASE_URL user/db from `shieldai` to `kordant`, update `DD_SERVICE="shieldai-api"` to `DD_SERVICE="kordant-api"` -4. Edit `.env.prod.example` — update `GITHUB_REPOSITORY_OWNER=shieldai` to `GITHUB_REPOSITORY_OWNER=kordant` - -tests: -- Unit: Verify package.json `name` equals "kordant" -- Unit: Verify .env.example has no "shieldai" references - -acceptance_criteria: -- `cat package.json | jq .name` returns "kordant" -- `grep -rn "shieldai" .env.example .env.prod.example` returns empty -- `grep "DD_SERVICE" .env.example` shows "kordant-api" - -validation: -- Run `pnpm build` from root — should not fail (may have pre-existing errors unrelated to this change) -- Verify `pnpm --filter kordant*` style commands work diff --git a/tasks/rebrand-to-kordant/02-update-database-connection-strings.md b/tasks/rebrand-to-kordant/02-update-database-connection-strings.md deleted file mode 100644 index db4f3c2..0000000 --- a/tasks/rebrand-to-kordant/02-update-database-connection-strings.md +++ /dev/null @@ -1,39 +0,0 @@ -# 02. Update Database Connection Strings and DB Names - -meta: - id: rebrand-to-kordant-02 - feature: rebrand-to-kordant - priority: P1 - depends_on: [rebrand-to-kordant-01] - tags: [infrastructure, database] - -objective: -- Update all Turso database URLs and PostgreSQL connection strings from shieldai-* to kordant-* and database names from shieldai to kordant. - -deliverables: -- web/src/server/db/index.ts — Turso URL updated -- web/drizzle.config.ts — Turso URL updated -- web/.env.development — DATABASE_URL updated -- web/.env.example — DATABASE_URL db name updated -- .env.example — DATABASE_URL user/db updated -- .github/workflows/ci.yml — PostgreSQL credentials and DB name updated - -steps: -1. Edit `web/src/server/db/index.ts` — change `libsql://shieldai-dev-*` to `libsql://kordant-dev-*` -2. Edit `web/drizzle.config.ts` — same Turso URL update -3. Edit `web/.env.development` — same URL update -4. Edit `web/.env.example` — update `.../shieldai` postgres db name to `.../kordant` -5. Edit `.env.example` — update `POSTGRES_DB: shieldai`, `POSTGRES_USER: shieldai`, `POSTGRES_PASSWORD: shieldai_dev`, and `pg_isready -U shieldai`, and `DATABASE_URL` all to use `kordant` -6. Edit `.github/workflows/ci.yml` — update all `shieldai` references in db service config (POSTGRES_DB, POSTGRES_USER, POSTGRES_PASSWORD, pg_isready, DATABASE_URL) - -tests: -- Unit: grep for any remaining `shieldai.*turso.io` patterns — should be zero -- Config: verify all DATABASE_URL references use `kordant` db name - -acceptance_criteria: -- No `shieldai-dev-` or `shieldai` database name remains in any connection string -- CI workflow uses `kordant` as PostgreSQL database name - -validation: -- Run `grep -rn "libsql://shieldai" web/` — expect zero results -- Run `grep -rn "POSTGRES_DB: shieldai" .github/` — expect zero results diff --git a/tasks/rebrand-to-kordant/03-update-web-package-scope-and-imports.md b/tasks/rebrand-to-kordant/03-update-web-package-scope-and-imports.md deleted file mode 100644 index d4c11b9..0000000 --- a/tasks/rebrand-to-kordant/03-update-web-package-scope-and-imports.md +++ /dev/null @@ -1,39 +0,0 @@ -# 03. Update @shieldai/* Package Scopes and Web Imports - -meta: - id: rebrand-to-kordant-03 - feature: rebrand-to-kordant - priority: P0 - depends_on: [rebrand-to-kordant-01] - tags: [infrastructure, packages] - -objective: -- Rename all `@shieldai/*` package scopes to `@kordant/*` across the monorepo and update all corresponding import statements. - -deliverables: -- browser-ext/package.json — `"name": "@shieldai/browser-ext"` → `"@kordant/browser-ext"` -- All files importing `@shieldai/*` — import paths updated to `@kordant/*` -- All plan files referencing `@shieldai/*` — updated - -steps: -1. Edit `browser-ext/package.json` — change package scope from `@shieldai` to `@kordant` -2. Search and replace all `@shieldai/` → `@kordant/` across: - - `web/src/**/*.ts` - - `web/src/**/*.tsx` - - `browser-ext/**/*.ts` - - `browser-ext/**/*.tsx` - - `plans/*.md` -3. Verify no `@shieldai/` references remain in source code - -tests: -- Build: `pnpm build` succeeds (after scope update) -- Grep: `grep -rn "@shieldai/" web/src/ browser-ext/src/` returns empty - -acceptance_criteria: -- `pnpm --filter @kordant/browser-ext ...` commands resolve correctly -- All import statements across the monorepo use `@kordant/` scope -- No `@shieldai/` string remains in any package.json or source file - -validation: -- Run `grep -rn "from \"@shieldai/" web/src/ browser-ext/src/` — expect zero -- Run `grep -rn "@shieldai/" package.json browser-ext/package.json` — expect zero diff --git a/tasks/rebrand-to-kordant/04-update-web-ui-brand-text.md b/tasks/rebrand-to-kordant/04-update-web-ui-brand-text.md deleted file mode 100644 index 7e4dfc2..0000000 --- a/tasks/rebrand-to-kordant/04-update-web-ui-brand-text.md +++ /dev/null @@ -1,46 +0,0 @@ -# 04. Replace "ShieldAI" Display Text Across Web UI - -meta: - id: rebrand-to-kordant-04 - feature: rebrand-to-kordant - priority: P1 - depends_on: [rebrand-to-kordant-03] - tags: [frontend, ui] - -objective: -- Replace all user-visible "ShieldAI" branding text across web UI components with "Kordant". - -deliverables: -- Page titles in all route files updated (app.tsx, index.tsx, dashboard, auth, blog, ads, 404) -- Navbar, Sidebar, Footer, AppShell brand text updated -- Landing page (HeroSection, WhyShieldAISection, CTABannerSection) updated -- Auth layout testimonial text and brand references updated -- Ads page copy updated -- Tests updated to reference "Kordant" instead of "ShieldAI" - -steps: -1. Update all `` tags in route files — replace "ShieldAI" with "Kordant" in page title strings -2. Update Navbar.tsx — replace "ShieldAI" brand text -3. Update Sidebar.tsx — replace "ShieldAI" brand text -4. Update Footer.tsx — replace "ShieldAI" and "ShieldAI. All rights reserved." -5. Update AppShell.tsx — replace default title -6. Update HeroSection.tsx — replace "ShieldAI evens" -7. Update WhyShieldAISection — rename component, id, and text references -8. Update CTABannerSection — replace "trust ShieldAI" -9. Update landing index.ts export and imports in routes/index.tsx -10. Update AuthLayout.tsx — replace testimonial/quote brand text -11. Update onboarding.tsx — replace "Your ShieldAI account is ready..." -12. Update ads.tsx — replace all marketing copy -13. Update all test files that assert "ShieldAI" in rendered output - -tests: -- Unit: All tests pass after text changes -- Visual: Web app renders "Kordant" in all branded locations - -acceptance_criteria: -- No "ShieldAI" text appears in any web UI page title, nav, sidebar, footer, landing, auth, blog, or ads page -- Test assertions updated to expect "Kordant" instead of "ShieldAI" - -validation: -- Run `grep -rn "ShieldAI" web/src/components/ web/src/routes/` — check only legitimate remaining references (comments, third-party) -- Run `pnpm --filter web test` — all tests pass diff --git a/tasks/rebrand-to-kordant/05-update-web-email-notification-templates.md b/tasks/rebrand-to-kordant/05-update-web-email-notification-templates.md deleted file mode 100644 index 65600a3..0000000 --- a/tasks/rebrand-to-kordant/05-update-web-email-notification-templates.md +++ /dev/null @@ -1,46 +0,0 @@ -# 05. Update Email Templates, Notification Content, and Queue Names - -meta: - id: rebrand-to-kordant-05 - feature: rebrand-to-kordant - priority: P1 - depends_on: [rebrand-to-kordant-01] - tags: [backend, email] - -objective: -- Replace all "ShieldAI" references in email templates, notification services, report generators, and BullMQ queue names. - -deliverables: -- email.templates.ts — brand text, subjects, bodies, headers, footers updated -- email.templates.test.ts — test assertions updated -- notification.service.ts — from address updated to kordant.ai -- alert.publisher.ts — push notification title prefix updated -- reports.service.ts — report title updated -- reports/generator.ts — report text updated -- reports/templates/*.html — HTML report templates updated -- queue.ts — BullMQ queue name updated from "shieldai-jobs" to "kordant-jobs" -- waitlist-email-sequence-implementation.md — domain references updated - -steps: -1. Edit email.templates.ts — replace all "ShieldAI" brand text in subjects, HTML, plain text bodies, footers, team signatures -2. Edit email.templates.ts — update from/noreply email to `noreply@kordant.ai` -3. Edit email.templates.test.ts — update test assertions to expect "Kordant" -4. Edit notification.service.ts — update `noreply@shieldai.app` → `noreply@kordant.ai` -5. Edit alert.publisher.ts — update `` `[ShieldAI] ${alert.title}` `` → `` `[Kordant] ${alert.title}` `` -6. Edit reports.service.ts — update report title from "ShieldAI" to "Kordant" -7. Edit reports/generator.ts — update all brand text references -8. Edit reports/templates/*.html — update title and footer in weekly-digest, monthly-plus, annual-premium -9. Edit queue.ts — update queue name "shieldai-jobs" → "kordant-jobs" - -tests: -- Unit: Email template tests pass with new brand name -- Unit: Report generator tests pass - -acceptance_criteria: -- No "ShieldAI" remains in any email template, notification, or report -- Email from address uses `@kordant.ai` -- BullMQ queue name uses "kordant-jobs" - -validation: -- Run `grep -rn "ShieldAI" web/src/server/services/` — expect zero results -- Run `grep "shieldai-jobs" web/src/server/jobs/queue.ts` — expect zero diff --git a/tasks/rebrand-to-kordant/06-update-web-storage-keys-and-constants.md b/tasks/rebrand-to-kordant/06-update-web-storage-keys-and-constants.md deleted file mode 100644 index 0e74093..0000000 --- a/tasks/rebrand-to-kordant/06-update-web-storage-keys-and-constants.md +++ /dev/null @@ -1,37 +0,0 @@ -# 06. Update Browser Storage Keys, Theme Keys, and User-Agent Headers - -meta: - id: rebrand-to-kordant-06 - feature: rebrand-to-kordant - priority: P2 - depends_on: [rebrand-to-kordant-03] - tags: [frontend, config] - -objective: -- Update all localStorage keys, theme storage keys, unread count keys, device name strings, and user-agent headers from ShieldAI to Kordant. - -deliverables: -- web/src/lib/theme.tsx — STORAGE_KEY updated -- web/src/lib/theme.test.ts — assertions updated (11 occurrences) -- web/src/entry-server.tsx — theme key updated -- web/src/hooks/useRealtimeAlerts.ts — UNREAD_STORAGE_KEY updated -- web/src/server/api/routers/extension.ts — "ShieldAI Browser Extension" updated -- web/src/server/services/darkwatch/scan.engine.ts — user-agent header updated - -steps: -1. Edit `web/src/lib/theme.tsx` — change `STORAGE_KEY = "shieldai-theme"` to `"kordant-theme"` -2. Edit `web/src/lib/theme.test.ts` — update all `"shieldai-theme"` assertions to `"kordant-theme"` -3. Edit `web/src/entry-server.tsx` — update `'shieldai-theme'` to `'kordant-theme'` -4. Edit `web/src/hooks/useRealtimeAlerts.ts` — update `UNREAD_STORAGE_KEY = "shieldai_unread_count"` to `"kordant_unread_count"` -5. Edit `web/src/server/api/routers/extension.ts` — update device name from `"ShieldAI Browser Extension"` to `"Kordant Browser Extension"` -6. Edit `web/src/server/services/darkwatch/scan.engine.ts` — update `"user-agent": "ShieldAI-DarkWatch"` to `"Kordant-DarkWatch"` - -tests: -- Unit: Theme tests pass after key update -- Integration: DarkWatch scanner user-agent header updated - -acceptance_criteria: -- No `shieldai-theme`, `shieldai_unread_count`, `ShieldAI Browser Extension`, or `ShieldAI-DarkWatch` remain in source - -validation: -- Run `grep -rn "shieldai-theme\|shieldai_unread_count\|ShieldAI Browser Extension\|ShieldAI-DarkWatch" web/src/` — expect zero diff --git a/tasks/rebrand-to-kordant/07-update-web-seed-data-and-blog-content.md b/tasks/rebrand-to-kordant/07-update-web-seed-data-and-blog-content.md deleted file mode 100644 index d582567..0000000 --- a/tasks/rebrand-to-kordant/07-update-web-seed-data-and-blog-content.md +++ /dev/null @@ -1,32 +0,0 @@ -# 07. Update Seed Data, Blog Content, and Landing Page Copy - -meta: - id: rebrand-to-kordant-07 - feature: rebrand-to-kordant - priority: P2 - depends_on: [rebrand-to-kordant-03] - tags: [frontend, content] - -objective: -- Update seed data references, blog content, and remaining landing page copy from ShieldAI to Kordant. - -deliverables: -- web/src/server/db/seed.ts — author name and description updated -- web/src/routes/blog.tsx — blog page title, post titles, copy updated -- web/src/routes/blog/[slug].tsx — blog post content, metadata, author updated - -steps: -1. Edit `web/src/server/db/seed.ts` — update `authorName: "ShieldAI Team"` to `"Kordant Team"`, update description text -2. Edit `web/src/routes/blog.tsx` — update page title, blog descriptions, post slugs containing "shieldai", post titles -3. Edit `web/src/routes/blog/[slug].tsx` — update all brand references in blog post content, metadata, author role - -tests: -- Unit: Seed data tests pass -- Unit: Blog page renders correctly with updated content - -acceptance_criteria: -- No "ShieldAI" remains in seed data or blog content -- Blog post slugs no longer contain "shieldai" - -validation: -- Run `grep -rn "ShieldAI" web/src/server/db/seed.ts web/src/routes/blog.tsx web/src/routes/blog/` — expect zero diff --git a/tasks/rebrand-to-kordant/08-update-browser-extension-branding.md b/tasks/rebrand-to-kordant/08-update-browser-extension-branding.md deleted file mode 100644 index 72c7871..0000000 --- a/tasks/rebrand-to-kordant/08-update-browser-extension-branding.md +++ /dev/null @@ -1,47 +0,0 @@ -# 08. Update Browser Extension Branding - -meta: - id: rebrand-to-kordant-08 - feature: rebrand-to-kordant - priority: P1 - depends_on: [rebrand-to-kordant-01, rebrand-to-kordant-03] - tags: [browser-ext, frontend] - -objective: -- Update all ShieldAI branding in the browser extension: manifest, HTML pages, storage keys, log messages, and domain references. - -deliverables: -- browser-ext/public/manifest.json — name and host permissions updated -- browser-ext/src/popup/popup.html — title and brand text updated -- browser-ext/src/options/options.html — title, heading, API URL placeholder updated -- browser-ext/src/options/options.ts — device name updated -- browser-ext/src/background/index.ts — device name, log prefixes, storage keys, error messages updated -- browser-ext/src/content/index.ts — log prefixes updated -- browser-ext/src/lib/settings.ts — STORAGE_KEY and API URL updated -- browser-ext/src/lib/phishing-detector.ts — phishing domain reference data updated -- browser-ext/tests/ — all test assertions updated - -steps: -1. Edit `manifest.json` — update `"name": "ShieldAI"` to `"Kordant"`, update host permissions from `*.shieldai.com` to `*.kordant.ai` -2. Edit `popup.html` — update `<title>` and `<span>ShieldAI</span>` -3. Edit `options.html` — update title, heading, API URL placeholder -4. Edit `options.ts` — update device name -5. Edit `background/index.ts` — update device name, log prefixes, storage keys, error messages -6. Edit `content/index.ts` — update log prefixes -7. Edit `lib/settings.ts` — update STORAGE_KEY and base URL -8. Edit `lib/phishing-detector.ts` — update phishing domain examples from shieldai-* to kordant-* -9. Edit all test files — update assertions - -tests: -- Unit: Extension tests pass -- Manual: Load unpacked extension in Chrome — verify "Kordant" displays correctly - -acceptance_criteria: -- Extension manifest name shows "Kordant" -- All storage keys use `kordant:` prefix -- No `shieldai` / `ShieldAI` remains in extension source -- API domain uses `kordant.ai` - -validation: -- Run `grep -rn "shieldai\|ShieldAI" browser-ext/` — expect zero -- Load extension in Chrome, verify name in toolbar diff --git a/tasks/rebrand-to-kordant/09-update-ios-app-branding.md b/tasks/rebrand-to-kordant/09-update-ios-app-branding.md deleted file mode 100644 index 05aacc5..0000000 --- a/tasks/rebrand-to-kordant/09-update-ios-app-branding.md +++ /dev/null @@ -1,47 +0,0 @@ -# 09. Update iOS App Branding - -meta: - id: rebrand-to-kordant-09 - feature: rebrand-to-kordant - priority: P1 - depends_on: [rebrand-to-kordant-01] - tags: [ios, mobile] - -objective: -- Update all ShieldAI branding in the iOS app: Xcode project, Swift source files, bundle identifiers, display text, storage keys, and URL schemes. - -deliverables: -- iOS/ShieldAI/ directory renamed to iOS/Kordant/ -- Xcode project file (.pbxproj) — all ShieldAI references updated (bundle IDs, target names, product names, entitlements, URL schemes, info plist strings) -- ShieldAIApp.swift — app struct renamed -- ShieldAITheme.swift — theme struct renamed; ShieldAITheme.cornerRadius references in all View files updated -- AuthView, OnboardingView — display text updated -- BiometricAuthService — auth prompt messages updated -- CameraService — usage description strings updated -- APIClient, PushNotificationService, NetworkMonitor — subsystem strings updated -- Route.swift — URL scheme updated from "shieldai" to "kordant" -- ThemeManager — storage key updated -- OfflineQueue, CacheManager — storage keys updated -- ShieldAITests, ShieldAIUITests — all references updated -- Deep link URL strings in tests updated - -steps: -1. Rename iOS directory from `iOS/ShieldAI` to `iOS/Kordant` (this will require updating all paths in pbxproj) -2. Edit pbxproj — find/replace all ShieldAI → Kordant (bundle IDs, target names, product names, paths, info plist strings, URL schemes) -3. Edit Swift source files — update ShieldAIApp, ShieldAITheme, display text, storage keys, URL schemes, subsystem strings, biometric prompts -4. Update test files — @testable import, class names, URL strings - -tests: -- Build: iOS project opens in Xcode and builds successfully -- Unit: All iOS tests pass - -acceptance_criteria: -- Xcode project loads without errors -- All build targets compile -- Bundle identifier uses `com.kordant.*` -- URL scheme is `kordant://` -- No "ShieldAI" remains in any Swift source file - -validation: -- Run `grep -rn "ShieldAI\|shieldai" iOS/Kordant/ — expect zero -- Open project in Xcode, verify build succeeds diff --git a/tasks/rebrand-to-kordant/10-update-android-app-branding.md b/tasks/rebrand-to-kordant/10-update-android-app-branding.md deleted file mode 100644 index a25eaf7..0000000 --- a/tasks/rebrand-to-kordant/10-update-android-app-branding.md +++ /dev/null @@ -1,54 +0,0 @@ -# 10. Update Android App Branding - -meta: - id: rebrand-to-kordant-10 - feature: rebrand-to-kordant - priority: P1 - depends_on: [rebrand-to-kordant-01] - tags: [android, mobile] - -objective: -- Update all ShieldAI branding in the Android app: package names, Kotlin source files, resources, build config, and class names. - -deliverables: -- android/ShieldAI/ directory renamed to android/Kordant/ -- build.gradle.kts — namespace, applicationId, and API URLs updated -- settings.gradle.kts — rootProject.name updated -- All Kotlin source files — package declarations updated from `com.shieldai.android.*` to `com.kordant.*` -- ShieldAIApp.kt renamed to KordantApp.kt -- ShieldAIDatabase.kt renamed to KordantDatabase.kt — DATABASE_NAME updated -- All import statements across ~70+ Kotlin files updated -- AndroidManifest.xml — app class name, theme references updated -- strings.xml — app_name updated -- themes.xml — style name updated -- Storage key strings updated (shieldai_database, shieldai_auth_prefs, shieldai_biometric_prefs) -- UI display text strings updated (ShieldAI → Kordant) -- Theme.kt — ShieldAITheme fun renamed to KordantTheme - -steps: -1. Rename directory from `android/ShieldAI` to `android/Kordant` -2. Edit build.gradle.kts — update namespace, applicationId, API URLs -3. Edit settings.gradle.kts — update rootProject.name -4. Update all Kotlin package declarations from `com.shieldai.android` to `com.kordant` -5. Update all import statements referencing `com.shieldai.android.*` to `com.kordant.*` -6. Rename ShieldAIApp.kt → KordantApp.kt (update class name) -7. Rename ShieldAIDatabase.kt → KordantDatabase.kt (update class name and DATABASE_NAME) -8. Update AndroidManifest.xml — android:name, theme, app_name -9. Update strings.xml, themes.xml -10. Update Theme.kt — fun ShieldAITheme → fun KordantTheme -11. Update all storage keys from shieldai_* to kordant_* -12. Update all UI display text (ComponentShowcase, AuthScreen, BiometricAuthScreen) - -tests: -- Build: Android project builds successfully -- Unit: All Android tests pass - -acceptance_criteria: -- Android project compiles without errors -- Application ID is `com.kordant.*` -- No "ShieldAI" or "shieldai" remains in any Kotlin source, XML resource, or build config -- Database name uses "kordant_database" - -validation: -- Run `grep -rn "ShieldAI\|shieldai\|com\.shieldai" android/Kordant/` — expect zero -- Open project in Android Studio, verify build succeeds diff --git a/tasks/rebrand-to-kordant/11-update-ci-cd-and-infrastructure.md b/tasks/rebrand-to-kordant/11-update-ci-cd-and-infrastructure.md deleted file mode 100644 index aca42f6..0000000 --- a/tasks/rebrand-to-kordant/11-update-ci-cd-and-infrastructure.md +++ /dev/null @@ -1,39 +0,0 @@ -# 11. Update CI/CD and Infrastructure References - -meta: - id: rebrand-to-kordant-11 - feature: rebrand-to-kordant - priority: P1 - depends_on: [rebrand-to-kordant-01] - tags: [infrastructure, ci-cd] - -objective: -- Update all CI/CD workflow files, Docker image tags, Terraform state bucket names, ECS cluster names, and service names from ShieldAI to Kordant. - -deliverables: -- .github/workflows/ci.yml — Docker tags, coverage artifact name, service names updated -- .github/workflows/deploy.yml — bucket name, image tags, cluster names, service names all updated -- scripts/setup-ga4.sh — display name, property name, domain references updated -- scripts/load-test/run-all.sh — test description and service references updated - -steps: -1. Edit `.github/workflows/ci.yml` — update all `shieldai:` Docker tags to `kordant:`, update `name: shieldai-coverage` to `kordant-coverage` -2. Edit `.github/workflows/deploy.yml` — update: - - S3 bucket `shieldai-*` to `kordant-*` - - Docker image tags `shieldai-${{ matrix.name }}` to `kordant-${{ matrix.name }}` - - ECS cluster names `shieldai-${ENV}` to `kordant-${ENV}` - - Service name references -3. Edit `scripts/setup-ga4.sh` — update display name from "ShieldAI" to "Kordant", domain from `shieldai.com` to `kordant.ai` -4. Edit `scripts/load-test/run-all.sh` — update "ShieldAI" references - -tests: -- None (CI/CD changes are validated on next pipeline run) - -acceptance_criteria: -- No `shieldai-` prefix remains in any CI/CD workflow for Docker tags, cluster names, or bucket names -- GA4 script uses kordant.ai domain -- Load test script references Kordant - -validation: -- Run `grep -rn "shieldai-\.*" .github/workflows/ scripts/` — expect zero -- Review updated deploy.yml for consistency diff --git a/tasks/rebrand-to-kordant/12-update-assets-and-ad-creatives.md b/tasks/rebrand-to-kordant/12-update-assets-and-ad-creatives.md deleted file mode 100644 index fc7f84a..0000000 --- a/tasks/rebrand-to-kordant/12-update-assets-and-ad-creatives.md +++ /dev/null @@ -1,40 +0,0 @@ -# 12. Update SVG Ad Creatives and Marketing Assets - -meta: - id: rebrand-to-kordant-12 - feature: rebrand-to-kordant - priority: P3 - depends_on: [] - tags: [assets, marketing] - -objective: -- Update all SVG ad creative files in the assets/ directory to replace "ShieldAI" branding with "Kordant". - -deliverables: -- assets/ads/meta_c_1x1_1080x1080.svg — headline updated -- assets/ads/meta_b_45_1080x1350.svg — terminal prompt, body text, tagline updated -- assets/ads/meta_b_1x1_1080x1080.svg — terminal prompt, product name updated -- assets/ads/meta_a_1x1_1080x1080.svg — headline updated -- assets/ads/linkedin/variant1_professional.svg — body text, logo updated -- assets/ads/linkedin/variant2_datasecurity.svg — product name, logo updated -- assets/ads/linkedin/variant3_family_professional.svg — badge, logo updated -- assets/ads/gd_portrait_600x750.svg — tagline updated - -steps: -1. For each SVG file, search for "ShieldAI" text nodes and replace with "Kordant" -2. For terminal prompts like `darkwatch@shieldai:~$`, update to `darkwatch@kordant:~$` -3. For taglines like "ShieldAI — AI-Powered Identity Protection", update to "Kordant — AI-Powered Identity Protection" -4. Verify SVG files remain valid after replacements - -tests: -- Visual: Open SVGs in browser — verify text renders correctly with new name -- Structural: SVGs remain valid XML - -acceptance_criteria: -- No "ShieldAI" text remains in any SVG asset file -- Terminal prompts use `@kordant` instead of `@shieldai` -- Taglines and headlines use "Kordant" - -validation: -- Run `grep -rn "ShieldAI" assets/ads/` — expect zero -- Run `grep -rn "shieldai" assets/ads/` — expect zero (check for prompt variants) diff --git a/tasks/rebrand-to-kordant/13-update-documentation-and-plan-files.md b/tasks/rebrand-to-kordant/13-update-documentation-and-plan-files.md deleted file mode 100644 index 9d594df..0000000 --- a/tasks/rebrand-to-kordant/13-update-documentation-and-plan-files.md +++ /dev/null @@ -1,39 +0,0 @@ -# 13. Update READMEs, Plan Documents, and Task References - -meta: - id: rebrand-to-kordant-13 - feature: rebrand-to-kordant - priority: P3 - depends_on: [rebrand-to-kordant-01] - tags: [documentation] - -objective: -- Update all README files, plan documents, and task directory references from ShieldAI to Kordant. - -deliverables: -- README.md — title, description, package references, plan file links updated -- plans/SHIELDAI-product-plan.md — filename renamed to plans/KORDANT-product-plan.md, content updated -- plans/SHIELDAI-technical-architecture.md — filename renamed, content updated -- tasks/shieldai-unified-restructure/ — directory renamed to tasks/kordant-unified-restructure/, all task ID frontmatter updated -- tasks/clerk-integration/README.md — reference to "ShieldAI/web" updated - -steps: -1. Rename plan files: `SHIELDAI-product-plan.md` → `KORDANT-product-plan.md`, `SHIELDAI-technical-architecture.md` → `KORDANT-technical-architecture.md` -2. Update README.md — replace "ShieldAI" with "Kordant" throughout, update plan file links -3. Rename task directory `tasks/shieldai-unified-restructure/` → `tasks/kordant-unified-restructure/` -4. Update all task file frontmatter in `tasks/kordant-unified-restructure/` — change `feature: shieldai-unified-restructure` and task IDs -5. Update `tasks/clerk-integration/README.md` — reference to ShieldAI/web -6. Update README filter package references: `@shieldai/spamshield` → `@kordant/spamshield`, etc. - -tests: -- None (documentation only) - -acceptance_criteria: -- README.md no longer mentions "ShieldAI" -- Plan filenames use Kordant prefix -- Task directory names use kordant prefix -- All internal task references updated - -validation: -- Run `grep -rn "ShieldAI\|shieldai" README.md plans/` — expect zero -- Verify plan files compile/make sense with new name diff --git a/tasks/rebrand-to-kordant/14-verify-rebrand-completeness.md b/tasks/rebrand-to-kordant/14-verify-rebrand-completeness.md deleted file mode 100644 index 8659014..0000000 --- a/tasks/rebrand-to-kordant/14-verify-rebrand-completeness.md +++ /dev/null @@ -1,63 +0,0 @@ -# 14. Final Sweep and Verification of Rebrand Completeness - -meta: - id: rebrand-to-kordant-14 - feature: rebrand-to-kordant - priority: P0 - depends_on: [rebrand-to-kordant-13] - tags: [quality-assurance] - -objective: -- Perform a comprehensive final sweep of the entire codebase to verify zero "ShieldAI" or "shieldai" references remain in source code (excluding git history and third-party dependencies), and that all builds and tests pass. - -deliverables: -- Final verification report -- Any straggling references cleaned up - -steps: -1. Run comprehensive grep searches across the repo for "ShieldAI" and "shieldai", filtering out: - - `.git/` directory - - `node_modules/` - - `pnpm-lock.yaml`, `bun.lock` - - Any false positives (e.g., in git history references) -2. For each remaining reference, determine if it needs updating -3. Run `pnpm build` — verify all packages build -4. Run `pnpm test` — verify all tests pass -5. Verify web app renders correctly with "Kordant" branding -6. Verify browser extension loads with new name -7. Review all changed files for consistency (punctuation, casing, sentence flow) -8. Run a second sweep to catch any missed references - -tests: -- Build: `pnpm build` succeeds across all packages -- Unit: `pnpm test` passes across all packages -- Integration: Web app, extension, and mobile builds succeed - -acceptance_criteria: -- Zero `ShieldAI` or `shieldai` references remain in source code -- All packages build successfully -- All tests pass -- Domain `kordant.ai` is correctly referenced throughout - -validation: -```bash -# Final sweep commands -echo "=== ShieldAI (PascalCase) ===" -grep -rn "ShieldAI" --include="*.{ts,tsx,js,jsx,kt,swift,html,svg,yml,yaml,sh,env,toml,json}" . \ - --exclude-dir=.git --exclude-dir=node_modules --exclude-dir=.turbo \ - | grep -v "pnpm-lock\|bun.lock\|\.next\|dist" - -echo "=== shieldai (lowercase) ===" -grep -rn "shieldai" --include="*.{ts,tsx,js,jsx,kt,swift,html,svg,yml,yaml,sh,env,toml,json}" . \ - --exclude-dir=.git --exclude-dir=node_modules --exclude-dir=.turbo \ - | grep -v "pnpm-lock\|bun.lock\|\.next\|dist" - -echo "=== SHIELD (pattern check) ===" -grep -rn "SHIELD" --include="*.{ts,tsx,kt,swift}" . \ - --exclude-dir=.git --exclude-dir=node_modules | grep -v "nodemon" -``` - -notes: -- The `.git` directory contains the old name in commit history — that's expected and should NOT be changed -- Third-party dependencies in `node_modules/` may reference "shieldai" in cached packages — ignore these -- The `tasks/` directory contains historical task files with "shieldai" IDs — these are internal planning docs and may optionally be skipped diff --git a/tasks/rebrand-to-kordant/README.md b/tasks/rebrand-to-kordant/README.md deleted file mode 100644 index 7dea9b0..0000000 --- a/tasks/rebrand-to-kordant/README.md +++ /dev/null @@ -1,42 +0,0 @@ -# Rebrand ShieldAI → Kordant - -Objective: Complete rebrand of the ShieldAI platform to Kordant across all packages, apps, infrastructure, and assets. - -Status legend: [ ] todo, [~] in-progress, [x] done - -Tasks -- [x] 01 — Update monorepo foundation (root package, env, config) → `01-update-monorepo-foundation.md` -- [x] 02 — Update database connection strings and DB names → `02-update-database-connection-strings.md` -- [x] 03 — Update @shieldai/* package scopes and web imports → `03-update-web-package-scope-and-imports.md` -- [x] 04 — Replace "ShieldAI" display text across web UI → `04-update-web-ui-brand-text.md` -- [x] 05 — Update email templates, notification content, and queue names → `05-update-web-email-notification-templates.md` -- [x] 06 — Update browser storage keys, theme keys, and user-agent headers → `06-update-web-storage-keys-and-constants.md` -- [x] 07 — Update seed data, blog content, and landing page copy → `07-update-web-seed-data-and-blog-content.md` -- [x] 08 — Update browser extension manifest, HTML, and storage keys → `08-update-browser-extension-branding.md` -- [x] 09 — Update iOS bundle ID, Xcode project, and Swift source branding → `09-update-ios-app-branding.md` -- [x] 10 — Update Android package names, Kotlin source, and resources → `10-update-android-app-branding.md` -- [x] 11 — Update CI/CD workflows, Docker tags, and service names → `11-update-ci-cd-and-infrastructure.md` -- [x] 12 — Update SVG ad creatives and marketing assets → `12-update-assets-and-ad-creatives.md` -- [x] 13 — Update READMEs, plan documents, and task references → `13-update-documentation-and-plan-files.md` -- [x] 14 — Final sweep and verification of rebrand completeness → `14-verify-rebrand-completeness.md` - -Dependencies -- 01 → 03 (package scope must be updated first before web imports) -- 03 → 04 (web scope must resolve before changing text) -- 01 → 05 (env changes needed for email domain) -- 01 → 08 (root package affects extension) -- 01 → 11 (env vars feed CI/CD) -- 03 → 06 (package resolution needed) -- 01 → 09 (root package naming) -- 01 → 10 (root package naming) -- 13 → 14 (docs update before final sweep) - -Exit criteria -- Zero occurrences of "ShieldAI" or "shieldai" remain in source code (excluding third-party dependencies and git history) -- All packages build successfully (`pnpm build`) -- All tests pass (`pnpm test`) -- iOS and Android projects open without errors in Xcode / Android Studio -- Browser extension loads successfully in Chrome -- Web app renders with "Kordant" branding throughout -- Domain `kordant.ai` is functional for all environments -- CI/CD pipelines reference kordant-* services and tags