[2026-10-06] eagle: work/projects/vscode-migration/modularization-roadmap.md

This commit is contained in:
Alexey Martemyanov committed 2026-10-06 16:58:05 +06:00
1 parent fa6b4e88f8
commit 05c78ca00d
1 file changed
+35 -34
@@ -5,7 +5,7 @@ Related: [[projects/vscode-migration/vscode-migration]] · [[projects/vscode-mig
## TL;DR
- The macOS browser code is now one SwiftPM target, `DuckDuckGo_Privacy_Browser`: **1,406 Swift files, ~271k lines, 103 feature folders**. It builds from Xcode and VSCode. **We are here.**
- The macOS browser code is now one big target, `DuckDuckGo_Privacy_Browser`: **1,406 Swift files, ~271k lines, 103 feature folders**.
- Inside that target almost everything depends on everything. 93 of the 103 folders form **one dependency cycle**. A module can't be split off until the cycle is broken. So the next work is mostly **removing a few hubs**, not moving files.
- Six hubs cause most of the coupling:
- the `AppDelegate` service locator: 273 files, ~45 services;
@@ -27,19 +27,19 @@ Related: [[projects/vscode-migration/vscode-migration]] · [[projects/vscode-mig
---
## Part 1: The road so far (VSCode migration)
## Part 1: The road so far
| # | Step | Result | Status |
|---|---|---|---|
| 1 | Move non-Swift code (ObjC/C) to local packages | App target is Swift-only | ✅ Jun 2025 |
| 2 | Missing imports, unused files cleanup | Files compile outside the app target | ✅ Jun 2025 |
| 3 | Unify WebKit private methods; drop `#if SPARKLE/APPSTORE` | DMG and App Store targets compile the same files | ✅ Mar 2026 |
| 4 | Drop dead target flags (`SANDBOX_TEST_TOOL`, `ALPHA`, `NETWORK_EXTENSION` in shared files) | One set of compilation conditions | ✅ #6983 |
| 5 | Replace sandbox-test-tool with App Store UI tests | No extra target compiles app sources | ✅ #6980 |
| 6 | Drop storyboards (Feedback, PasswordManager, BookmarksBar, NavigationBar) | No IB module bindings to the app module | ✅ / last one in review |
| 7 | Asset resource initializers (`NSImage(resource:)`) instead of generated symbol extensions | Asset catalogs work from a package | ✅ committed |
| 8 | **Target file split**: stages A–X below | No app file is compiled into a helper target | 🔄 A+B in review, C ready, G done; the rest is shared by path |
| 9 | **Browser code as a SwiftPM package** (static lib for the app, dylib for VSCode; strings, assets, Core Data models and JSON in `Bundle.module`) | `swift build` + SourceKit-LSP; run and debug from VSCode | 🔄 **← we are here** (PR #7080, draft) |
| # | Step | Result | Status |
| --- | ----------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------- | ------------------------------------------------------------- |
| 1 | Move non-Swift code (ObjC/C) to local packages | App target is Swift-only | ✅ Jun 2025 |
| 2 | Missing imports, unused files cleanup | Files compile outside the app target | ✅ Jun 2025 |
| 3 | Unify WebKit private methods; drop `#if SPARKLE/APPSTORE` | DMG and App Store targets compile the same files | ✅ Mar 2026 |
| 4 | Drop dead target flags (`SANDBOX_TEST_TOOL`, `ALPHA`, `NETWORK_EXTENSION` in shared files) | One set of compilation conditions | ✅ #6983 |
| 5 | Replace sandbox-test-tool with App Store UI tests | No extra target compiles app sources | ✅ #6980 |
| 6 | Drop storyboards (Feedback, PasswordManager, BookmarksBar, NavigationBar) | No IB module bindings to the app module | ✅ / last one in review |
| 7 | Asset resource initializers (`NSImage(resource:)`) instead of generated symbol extensions | Asset catalogs work from a package | ✅ PR in review |
| 8 | **Target file split**: stages A–X below | No app file is compiled into a helper target | 🔄 A+B in review, C ready, G done; the rest is shared by path |
| 9 | **Browser code as a SwiftPM package** (static lib for the app, dylib for VSCode; strings, assets, Core Data models and JSON in `Bundle.module`) | `swift build` + SourceKit-LSP; run and debug from VSCode | 🔄 **← we are here** (PR #7080, draft) |
### Target split stages (step 8)
@@ -327,27 +327,28 @@ Add these checks to a script and run it in CI:
Similarity = 1 − (differing lines / total lines of both files), with license headers stripped and whitespace ignored. Spot-checked: `TipKitController` differs by 1 line, `UnifiedMetadataCollector` by 3, `SyncMetricsEventsHandler` by 6, `ImportArchiveReader` by 14 of 138.
| Area | Near-duplicate files on both platforms | Destination | Verdict |
|---|---|---|---|
| **Sync app integration** | `SyncDataProviders` 70%, `SyncErrorHandler` 57%, `SyncBookmarksAdapter` 67%, `SyncCredentialsAdapter` 77%, `SyncCreditCardsAdapter` 83%, `SyncSettingsAdapter` 81%, `SyncMetadataDatabase` 72%, `SyncMetricsEventsHandler` 92%, cleanup error handlers 90–92%, `UnifiedDeviceListPixelHandler` 92%, `AIChatDeleteOperation` 95%, `FavoritesDisplayModeSyncHandler` 76%, `SyncPromoViewModel` 76% | new shared `SyncAppIntegration` package (or a BSK product) | **Share now.** ~3k duplicated lines; the biggest win |
| **Content blocking providers + ad blocking** | `ContentBlockerRulesLists` 81%, `AppTrackerDataSetProvider` 82%, `AppPrivacyConfigurationDataProvider` 75%, `AdBlockingAvailability` 92%, `AdBlockingNavigationHandler` 81% | BSK `ContentBlocking` / `PrivacyConfig` | **Share now.** The embedded config file is a parameter. The `ContentBlocking` composition root is shared after refactor |
| **Autoconsent (cookie popups)** | `AutoconsentUserScript` (779 / 746 lines, 80%), `AutoconsentPixels` 81%, `AutoconsentManagement` 37% | `WebExtensions/Autoconsent` or a BSK product | **Share now.** Settings and feature flags go behind protocols |
| **Feedback** | `UnifiedMetadataCollector` 97%, `UnifiedFeedbackFormViewModel` 72%, `FeedbackCategoryProviding` 72%, `UnifiedFeedbackSender` 47%, VPN/DBP metadata collectors 61–65% | new shared `Feedback` package (model + sender), UI per platform | **Share now** |
| **TipKit** | `TipKitController` 99% + 4 files at 89–99% | `UIComponents` or a new `TipKitSupport` | **Share now.** Also resolves part of stage E1 |
| **Data import** | `ImportArchiveReader` 94% (copy-paste), `AutofillLoginImportState` 60%; bookmark HTML parsing implemented twice (XMLParser on macOS, SwiftSoup on iOS) | BSK `DataImport` | **Share now** for the archive reader, HTML parser and import state. Browser-profile readers stay on macOS |
| **Duck.ai** | `DuckAiUsageWarningPixel` 90%, `AIChatSuggestionsReader` 84%, `AIChatFeatureFlagProvider` 83%, `PageContextExtractionPixelHandler` 80%, `PageContextUserScript` 63%, `DuckAiUsageLimitsStore` 51%; user-script handlers 35% | `AIChat/Shared` | **Share now** for stores, pixels and the suggestions reader. The user-script handler is shared after refactor |
| **Data clearing telemetry** | `DataClearingWideEventService` 64%, `DataClearingPixelsReporter` 50% | BSK `DataClearing` | **Share now.** The Fire engine is shared after refactor; iOS's worker-based `FireExecutor` is the better base than macOS's 1,324-line `Fire.swift` |
| **Remote messaging glue** | `DefaultRemoteMessagingSurfacesProvider` 92%, `OSUpgradeCapabilityOverride` 95%, store error handling 75%, matcher provider 55% | BSK `RemoteMessaging` | **Share now** (small) |
| **Small managers** | `AutofillNeverPromptWebsitesManager` 91%, `EmailManagerRequestDelegate` 76%, `SecureVaultReporter` 67%, `PrivacyStatsDatabase` 53%, `CrashReportSenderExtensions` 83%, `UserScriptError+Pixel` 87% | the matching BSK products | **Share now** (tiny PRs) |
| **Subscription web flows** | `SubscriptionPagesUserScript` 81%, `SubscriptionPagesUseSubscriptionFeature` (~1k lines each side, 40%), feature-flag adapters 83–95%, `SubscriptionPixelHandler` 76%, `FreeTrialPixelHandler` 85%, `SubscriptionEnvironment+Default` 80%, `VPNSettings+Environment` 92% | BSK `Subscription` with a `SubscriptionUIHandling` protocol | **Share after refactor.** The small adapters can be shared now. They are also stage **D**, so a shared home solves D for the helpers too |
| **Statistics / ATB** | `StatisticsLoader` 52%, `AtbParser` 85%, `AtbAndVariantCleanup` 86%, `StatisticsStore` 68%, `VariantManager` ~53% | BSK `Statistics` | **Share after refactor.** Storage goes through `KeyedStoring`. Pixel enums stay per platform |
| **Bookmarks** | DB setup, `BookmarksCleanupErrorHandling` 93%, `BookmarksExporter` 68%, legacy migration 52%. macOS has its own `LocalBookmarkStore` (1,314 lines) + `BookmarkManager` (841) on top of the shared model; iOS uses the BSK view models directly | BSK `Bookmarks` | **Share after refactor.** Move the store logic down, keep the tree/outline/pasteboard UI on macOS. `Bookmark` must stop holding `NSImage` and calling `faviconManager` |
| **History** | macOS `EncryptedHistoryStore` (463 lines) re-implements BSK `HistoryStore` (428) on its own encrypted `History.xcdatamodeld`; the schemas match except for encryption and entity names | BSK `History` with a pluggable encryption transformer | **Share after refactor.** Needs a macOS data migration |
| **Permissions** | Same domain, different designs: macOS `PermissionManager` + Core Data store (7 permission types); iOS `SitePermissions` package (KeyedStoring, 3 types) | make `SitePermissions` multi-platform, add the macOS types | **Share after refactor.** Needs a migration of macOS data |
| **Fireproofing** | Same API (`isAllowed(cookieDomain:)`, eTLD+1 migration); macOS stores in Core Data, iOS in UserDefaults | new shared `Fireproofing` with injected storage | **Share after refactor** (small, low risk) |
| **Duck Player** | `YoutubeOverlayUserScript` 53%, `YoutubePlayerUserScript` 51%; macOS code has no AppKit | BSK `DuckPlayer` | **Share after refactor** |
| **Default browser prompts** | prompt type deciders 45–60%, `UserActivity` model ~80% | make `SetDefaultBrowserCore` multi-platform, add the macOS dock prompt | **Share after refactor** |
| **Configuration, MaliciousSiteProtection** | `ConfigurationManager` 55%, `AppConfigurationURLProvider` 67%; MSP manager 28% | BSK `Configuration` / `MaliciousSiteProtection` | **Share after refactor** (small) |
| Area | Near-duplicate files on both platforms | Destination | Verdict |
| -------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ---------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Sync app integration** | `SyncDataProviders` 70%, `SyncErrorHandler` 57%, `SyncBookmarksAdapter` 67%, `SyncCredentialsAdapter` 77%, `SyncCreditCardsAdapter` 83%, `SyncSettingsAdapter` 81%, `SyncMetadataDatabase` 72%, `SyncMetricsEventsHandler` 92%, cleanup error handlers 90–92%, `UnifiedDeviceListPixelHandler` 92%, `AIChatDeleteOperation` 95%, `FavoritesDisplayModeSyncHandler` 76%, `SyncPromoViewModel` 76% | new shared `SyncAppIntegration` package (or a BSK product) | **Share now.** ~3k duplicated lines; the biggest win |
| **Content blocking providers + ad blocking** | `ContentBlockerRulesLists` 81%, `AppTrackerDataSetProvider` 82%, `AppPrivacyConfigurationDataProvider` 75%, `AdBlockingAvailability` 92%, `AdBlockingNavigationHandler` 81% | BSK `ContentBlocking` / `PrivacyConfig` | **Share now.** The embedded config file is a parameter. The `ContentBlocking` composition root is shared after refactor |
| **Autoconsent (cookie popups)** | `AutoconsentUserScript` (779 / 746 lines, 80%), `AutoconsentPixels` 81%, `AutoconsentManagement` 37% | `WebExtensions/Autoconsent` or a BSK product | **Share now.** Settings and feature flags go behind protocols |
| **Feedback** | `UnifiedMetadataCollector` 97%, `UnifiedFeedbackFormViewModel` 72%, `FeedbackCategoryProviding` 72%, `UnifiedFeedbackSender` 47%, VPN/DBP metadata collectors 61–65% | new shared `Feedback` package (model + sender), UI per platform | **Share now** |
| **TipKit** | `TipKitController` 99% + 4 files at 89–99% | `UIComponents` or a new `TipKitSupport` | **Share now.** Also resolves part of stage E1 |
| **Data import** | `ImportArchiveReader` 94% (copy-paste), `AutofillLoginImportState` 60%; bookmark HTML parsing implemented twice (XMLParser on macOS, SwiftSoup on iOS) | BSK `DataImport` | **Share now** for the archive reader, HTML parser and import state. Browser-profile readers stay on macOS |
| **Duck.ai** | `DuckAiUsageWarningPixel` 90%, `AIChatSuggestionsReader` 84%, `AIChatFeatureFlagProvider` 83%, `PageContextExtractionPixelHandler` 80%, `PageContextUserScript` 63%, `DuckAiUsageLimitsStore` 51%; user-script handlers 35% | `AIChat/Shared` | **Share now** for stores, pixels and the suggestions reader. The user-script handler is shared after refactor |
| **Data clearing telemetry** | `DataClearingWideEventService` 64%, `DataClearingPixelsReporter` 50% | BSK `DataClearing` | **Share now.** The Fire engine is shared after refactor; iOS's worker-based `FireExecutor` is the better base than macOS's 1,324-line `Fire.swift` |
| **Remote messaging glue** | `DefaultRemoteMessagingSurfacesProvider` 92%, `OSUpgradeCapabilityOverride` 95%, store error handling 75%, matcher provider 55% | BSK `RemoteMessaging` | **Share now** (small) |
| **Small managers** | `AutofillNeverPromptWebsitesManager` 91%, `EmailManagerRequestDelegate` 76%, `SecureVaultReporter` 67%, `PrivacyStatsDatabase` 53%, `CrashReportSenderExtensions` 83%, `UserScriptError+Pixel` 87% | the matching BSK products | **Share now** (tiny PRs) |
| **Subscription web flows** | `SubscriptionPagesUserScript` 81%, `SubscriptionPagesUseSubscriptionFeature` (~1k lines each side, 40%), feature-flag adapters 83–95%, `SubscriptionPixelHandler` 76%, `FreeTrialPixelHandler` 85%, `SubscriptionEnvironment+Default` 80%, `VPNSettings+Environment` 92% | BSK `Subscription` with a `SubscriptionUIHandling` protocol | **Share after refactor.** The small adapters can be shared now. They are also stage **D**, so a shared home solves D for the helpers too |
| **Statistics / ATB** | `StatisticsLoader` 52%, `AtbParser` 85%, `AtbAndVariantCleanup` 86%, `StatisticsStore` 68%, `VariantManager` ~53% | BSK `Statistics` | **Share after refactor.** Storage goes through `KeyedStoring`. Pixel enums stay per platform |
| **Bookmarks** | DB setup, `BookmarksCleanupErrorHandling` 93%, `BookmarksExporter` 68%, legacy migration 52%. macOS has its own `LocalBookmarkStore` (1,314 lines) + `BookmarkManager` (841) on top of the shared model; iOS uses the BSK view models directly | BSK `Bookmarks` | **Share after refactor.** Move the store logic down, keep the tree/outline/pasteboard UI on macOS. `Bookmark` must stop holding `NSImage` and calling `faviconManager` |
| **History** | macOS `EncryptedHistoryStore` (463 lines) re-implements BSK `HistoryStore` (428) on its own encrypted `History.xcdatamodeld`; the schemas match except for encryption and entity names | BSK `History` with a pluggable encryption transformer | **Share after refactor.** Needs a macOS data migration |
| **Permissions** | Same domain, different designs: macOS `PermissionManager` + Core Data store (7 permission types); iOS `SitePermissions` package (KeyedStoring, 3 types) | make `SitePermissions` multi-platform, add the macOS types | **Share after refactor.** Needs a migration of macOS data |
| **Fireproofing** | Same API (`isAllowed(cookieDomain:)`, eTLD+1 migration); macOS stores in Core Data, iOS in UserDefaults | new shared `Fireproofing` with injected storage | **Share after refactor** (small, low risk) |
| **Duck Player** | `YoutubeOverlayUserScript` 53%, `YoutubePlayerUserScript` 51%; macOS code has no AppKit | BSK `DuckPlayer` | **Share after refactor** |
| **Default browser prompts** | prompt type deciders 45–60%, `UserActivity` model ~80% | make `SetDefaultBrowserCore` multi-platform, add the macOS dock prompt | **Share after refactor** |
| **Configuration, MaliciousSiteProtection** | `ConfigurationManager` 55%, `AppConfigurationURLProvider` 67%; MSP manager 28% | BSK `Configuration` / `MaliciousSiteProtection` | **Share after refactor** (small) |
| | | | |
**Keep platform-specific:**
- Tab model: ~10% similar, different designs.