[pull] develop from duckduckgo:develop#350
Open
pull[bot] wants to merge 5655 commits intoRachelmorrell:developfrom
Open
[pull] develop from duckduckgo:develop#350pull[bot] wants to merge 5655 commits intoRachelmorrell:developfrom
pull[bot] wants to merge 5655 commits intoRachelmorrell:developfrom
Conversation
3bc8c57 to
ceb6fa4
Compare
…#7692) Task/Issue URL: https://app.asana.com/1/137249556945/task/1213149446740779 ### Description This PR fixes the context attachment when there isn’t a context attached properly. ### Steps to test this PR Enable contextualMode _Context not attached_ - [x] Navigate to malware.privacy-test-pages.site/security/badware/malware.html](http://malware.privacy-test-pages.site/security/badware/malware.html - [x] Open contextual mode - [x] Tap on Attach Page Context - [x] Verify nothing happens - [x] Enter any prompt, so Duck.ai opens - [x] Tap on Attach Page Context - [x] Verify nothing happens <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Low Risk** > Scoped logic change gated to contextual page-context handling, with expanded test coverage; main risk is minor UI/behavior regression in when `showContext` toggles. > > **Overview** > Prevents Duck.ai contextual *page context attachment* when the received context payload is missing required fields. `DuckChatContextualViewModel` now validates context JSON (requires non-blank `title`, `url`, and `content`) before storing it or showing the “Attach Page Context” UI, and clears cached context when invalid. > > Adjusts contextual behavior so `showContext` is only enabled on valid cached context (and respects the automatic-attachment flag without overriding a user-hidden state), and changes `DuckChatJSHelper.processJsCallbackMessage` to take a non-null `pageContext` defaulting to `""` with matching empty-check logic. Adds/updates unit tests to cover invalid context, auto-attach disabled, and attach actions with missing fields. > > <sup>Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit 81bd941. This will update automatically on new commits. Configure [here](https://cursor.com/dashboard?tab=bugbot).</sup> <!-- /CURSOR_SUMMARY -->
Task/Issue URL: https://app.asana.com/1/137249556945/project/1207908166761516/task/1212553599261552?focus=true ### Description This PR adds the ability for users to set a SERP Easter Egg logo as their favourite, which will then display on all DuckDuckGo search result pages. Features implemented: 1. Feature toggle - Added remote feature toggle (serpEasterEggLogos.setFavourite) to control the favourite logo functionality 2. Favourite logo data store - Added FavouriteSerpLogoDataStore to persist the user's favourite logo URL preference 3. Discovery screen enhancements - Updated the Easter Egg logo screen to: - Display a title telling the user they've discovered an Easter Egg logo - Added a button to set/unset the logo as favourite 4. Wiggle animation - Easter Egg logos now play a subtle wiggle animation when displayed in the omnibar: - Only plays once per unique logo URL - Skips animation for favourite logos - Waits for image crossfade to complete before animating 5. State management fixes - Fixed race conditions that could cause Easter Egg logos to be lost: - Preserve existing Easter Egg when onViewModeChanged is called - Distinguish between SerpLogo.Normal (explicitly no Easter Egg) and null (pending state) - Reset to Dax logo when favourite preference changes ### Steps to test this PR Easter Egg Discovery - [x] Search for a term that triggers an Easter Egg logo (e.g., "Southampton FC", "f1", "predator") - [x] Verify the Easter Egg logo appears in the omnibar with a wiggle animation - [x] Tap on the logo to open the enlarged view - [x] Verify the discovery text "You found a hidden logo!" is displayed at the top - [x] Verify the "Switch to This Logo" button is visible - [x] Tap anywhere outside to dismiss the view - [x] Search for another easter egg e.g. "hydra" and anticipate the wiggle animation - [x] As the wiggle animation plays click the logo - [x] The easter egg logo enlarged view should open and the logo should transition with no glitches - [x] Tap anywhere outside to dismiss the view - [x] The easter egg logo enlarged view should should transition back to the address with no glitches Setting a Favourite Logo - [x] Tap on the logo to open the enlarged view - [x] Open the Easter Egg logo screen - [x] Tap "Switch to This Logo" - [x] Tap anywhere outside to dismiss the view - [x] Search for something that does not have an easter egg logo e.g. "fpl" - [x] Verify the favourite logo persists in the omnibar - [x] Search for a term that triggers an Easter Egg logo (e.g., "Southampton FC", "f1", "predator") but is not your current favourite logo - [x] Verify the favourite logo persists in the omnibar - [x] Search for the same term for your favourite logo - [x] Verify the logo no longer plays the wiggle animation (since it's now a favourite) Resetting to Default - [x] With a favourite logo set, tap on the logo - [x] Verify the button now shows "Reset to Default" - [x] Tap "Reset Search Logo" - [x] Dismiss the easter egg logo screen - [x] Verify the DDG logo is visible - [x] Search for something that does not have an easter egg logo e.g. "fpl" - [x] Verify the DDG logo is still visible - [x] Search for a term that triggers an Easter Egg logo (e.g., "Southampton FC", "f1", "predator") - [x] Verify the Easter Egg logo appears in the omnibar with a wiggle animation Flag off ``` Index: serp-logos/serp-logos-api/src/main/kotlin/com/duckduckgo/serp/logos/api/SerpEasterEggLogosToggles.kt IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== diff --git a/serp-logos/serp-logos-api/src/main/kotlin/com/duckduckgo/serp/logos/api/SerpEasterEggLogosToggles.kt b/serp-logos/serp-logos-api/src/main/kotlin/com/duckduckgo/serp/logos/api/SerpEasterEggLogosToggles.kt --- a/serp-logos/serp-logos-api/src/main/kotlin/com/duckduckgo/serp/logos/api/SerpEasterEggLogosToggles.kt (revision 2a9b098) +++ b/serp-logos/serp-logos-api/src/main/kotlin/com/duckduckgo/serp/logos/api/SerpEasterEggLogosToggles.kt (date 1769694204288) @@ -24,6 +24,6 @@ @Toggle.DefaultValue(DefaultFeatureValue.FALSE) fun self(): Toggle - @Toggle.DefaultValue(DefaultFeatureValue.TRUE) + @Toggle.DefaultValue(DefaultFeatureValue.FALSE) fun setFavourite(): Toggle } ``` - [x] Set the logo you just searched for as a favourite - [x] Apply the patch above - [x] Install the app - [ ] You should see the logo you set as favourite if you did not navigate away before re-installation - [x] Search for a term that triggers an Easter Egg logo (e.g., "Southampton FC", "f1", "predator") - [x] The new easter egg logo should show and should not be your favourite (flag off means no favourite so user's don't get stuck with it if we need to turn off the flag) - [x] Tap the EasterEgg logo - [x] The Easter Egg Logo screen should open - [x] Verify the discovery text "You found a hidden logo!" is displayed - [x] Verify the "Switch to This Logo" button is **not** visible - [x] Dismiss the easter egg logo screen - [x] Search for something that does not have an easter egg logo e.g. "fpl" - [x] The DDG logo should show - [x] Navigate to a website e.g. ign.com - [x] The green shield should be visible - [x] General smoke test, do searches in SERP with easter eggs, do non easter egg searches, navigate away to websites. - [x] Favourite logo set before turning the flag off does not show ### UI changes | Before | After | | ------ | ----- | <img width="1080" height="2340" alt="before-light-mode" src="https://github.com/user-attachments/assets/9e5948a7-d499-48e7-b419-f0e50e06da2e" />|<img width="1080" height="2340" alt="after-light-mode" src="https://github.com/user-attachments/assets/0b50b621-f3c3-4055-a475-37f7eb7239a3" />| |<img width="1080" height="2340" alt="before-dark-mode" src="https://github.com/user-attachments/assets/68494542-17ec-47f0-8b55-70195b1589d5" />|<img width="1080" height="2340" alt="after-dark-mode" src="https://github.com/user-attachments/assets/303da02f-013c-4a91-81de-0d9370c289f5" />| <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Medium Risk** > Touches omnibar state/animation handling and adds new persistence + feature-flagged behaviour, so regressions could affect address bar logo rendering across navigation and page loads. Scope is contained to SERP-logo UI/state and is well covered by new/updated tests. > > **Overview** > Adds a **feature-flagged “favourite” SERP Easter Egg logo** that, when set, overrides normal SERP logo extraction and is shown on all DuckDuckGo search result pages. > > Introduces persistence via a new `FavouriteSerpLogoDataStore` (DataStore-backed) and wires it through `SerpLogos`/`BrowserTabViewModel` so favourite changes immediately update the omnibar and avoid issuing `ExtractSerpLogo` when a favourite is active. > > Updates the discovery UI to let users set/unset a favourite (new `SerpEasterEggLogoViewModel`, layout + strings) and refines omnibar rendering/animations: adds a one-time wiggle animation for non-favourite Easter Egg logos, plus explicit cancellation of that animation during logo transitions to prevent glitches. Tests are expanded across app + serp-logos modules, and `SerpLogo.EasterEgg` now carries an `isFavourite` flag to distinguish behaviour. > > <sup>Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit e3e334d. This will update automatically on new commits. Configure [here](https://cursor.com/dashboard?tab=bugbot).</sup> <!-- /CURSOR_SUMMARY -->
Task/Issue URL: https://app.asana.com/1/137249556945/project/1210856607616307/task/1213063476435699?focus=true ### Description Ditto ### Steps to test this PR Custom tabs pixel validation is passing for all `m.custom.tabs%` pixels: https://jenkins.duckduckgo.com/job/Privacy/job/Dev%20Pixel%20Validation/19/console <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Low Risk** > Dependency/version bump plus small JSON5 definition tweaks; main risk is stricter validation or changed suffix semantics affecting downstream analytics naming. > > **Overview** > Updates the PixelDefinitions tooling by bumping `@duckduckgo/pixel-schema` from `v1.0.8` to `v1.1.0` (with corresponding `package-lock.json` dependency refresh). > > Adjusts pixel definitions to match the new schema expectations: autocomplete “displayed” pixels now declare `triggers: ["other"]`, and custom tabs pixels replace `daily_count_short` with explicit suffix combinations (`["form_factor"]` and `["first_daily_count", "form_factor"]`). > > <sup>Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit fc108c4. This will update automatically on new commits. Configure [here](https://cursor.com/dashboard?tab=bugbot).</sup> <!-- /CURSOR_SUMMARY -->
Task/Issue URL: https://app.asana.com/1/137249556945/task/1213063474684597 ### Description Fixes pixel definitions for a few failing pixels. ### Steps to test this PR QA optional ### UI changes No UI changes <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Low Risk** > Metadata-only updates to pixel definition JSON and a version bump; main risk is analytics aggregation changes due to the new suffix dimension. > > **Overview** > Updates PIR pixel definitions in `personal_information_removal.json5` to include the `daily_count_short` suffix (alongside `form_factor`) across many opt-out/scan/engagement pixels, fixing previously failing definitions. > > Bumps `PixelDefinitions/product.json` target `appVersion` from `5.262.0` to `5.264.1`. > > <sup>Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit 6b37776. This will update automatically on new commits. Configure [here](https://cursor.com/dashboard?tab=bugbot).</sup> <!-- /CURSOR_SUMMARY -->
Task/Issue URL: https://app.asana.com/1/137249556945/task/1212975172246282 ### Description See attached task description ### Steps to test this PR Test in top most stack please. <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Medium Risk** > Changes WebView lifecycle/state-machine behavior and cookie/storage handling, which can affect broker automation reliability and run sequencing; however the pre-seeding path is best-effort and well covered by new unit/integration tests. > > **Overview** > Adds a new `PirWebViewDataCleaner` (with `RealPirWebViewDataCleaner`) and wires it into scan/opt-out/email-confirmation jobs to clear cookies and WebStorage after runs and on `stop`, plus enhances runner teardown to clear WebView form data/history/cache before destruction. > > Introduces a cookie *pre-seeding* flow in the actions state machine (`preseeding` flag, `PreSeedCookies` event, `PreseedCookiesEventHandler`) that loads a broker URL once before executing its steps for brokers in `PirJobConstants.preSeedList` (currently `Spokeo`), with load-complete/failure handlers updated to treat pre-seeding as best-effort and skip recovery on failure. > > Tweaks detached WebView configuration to avoid caching (`WebSettings.LOAD_NO_CACHE`) and updates/integrates tests and fakes to cover pre-seeding and cleanup behavior. > > <sup>Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit f5fb4d2. This will update automatically on new commits. Configure [here](https://cursor.com/dashboard?tab=bugbot).</sup> <!-- /CURSOR_SUMMARY -->
Task/Issue URL: https://app.asana.com/1/137249556945/task/1212993252484788 ### Description Adds support for entitlement target. See definition in https://app.asana.com/1/137249556945/project/1202552961248957/task/1213025097225385?focus=true ### Steps to test this PR https://app.asana.com/1/137249556945/task/1213103493701371 <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Medium Risk** > Changes the shape of `Toggle.State.Target` and adds new runtime targeting logic that can affect feature-flag enablement decisions; the entitlement matcher uses `runBlocking` over subscription flows, which could introduce blocking if invoked on performance-sensitive threads. > > **Overview** > Feature toggle targeting now supports an additional `entitlement` criterion via a new nullable field on `Toggle.State.Target`, with expanded API documentation describing matching semantics. > > Remote-feature codegen is updated to parse/serialize the `entitlement` value from remote config into stored toggle state, and a new `EntitlementTargetMatcherPlugin` (multibound in `AppScope`) matches targets against the user’s current subscription entitlements (case-insensitive). Existing target-matcher and codegen tests are updated, and new unit tests cover the entitlement matcher behavior. > > <sup>Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit 294e4dc. This will update automatically on new commits. Configure [here](https://cursor.com/dashboard?tab=bugbot).</sup> <!-- /CURSOR_SUMMARY -->
- Automated content scope scripts dependency update This PR updates the content scope scripts dependency to the latest available version and copies the necessary files. Tests will only run if something has changed in the `node_modules/@duckduckgo/content-scope-scripts` folder. If only the package version has changed, there is no need to run the tests. If tests have failed, see https://app.asana.com/0/1202561462274611/1203986899650836/f for further information on what to do next. _`content-scope-scripts` folder update_ - [ ] All tests must pass - [ ] Privacy tests must pass _Only `content-scope-scripts` package update_ - [ ] All tests must pass - [ ] Privacy tests do not need to run <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Medium Risk** > Large vendor bundle update that adds new periodic DOM/video polling on YouTube and changes breakage-report payloads, which could impact performance or detection behavior on a high-traffic site. > > **Overview** > Updates `@duckduckgo/content-scope-scripts` to `13.1.0` (and refreshes the bundled `node_modules` Android builds) via `package.json`/`package-lock.json`. > > The updated scripts expand `webInterferenceDetection` support across Android bundles and add a new YouTube-focused detector (`runYoutubeAdDetection`/`YouTubeAdDetector`) that tracks YouTube ad states, playability/adblocker modals, buffering metrics, and reports results into breakage reporting. > > Text-pattern matching is hardened by catching invalid regex patterns and introducing `toRegExpArray` to safely precompile pattern lists. > > <sup>Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit e68951b. This will update automatically on new commits. Configure [here](https://cursor.com/dashboard?tab=bugbot).</sup> <!-- /CURSOR_SUMMARY --> Co-authored-by: daxmobile <daxmobile@users.noreply.github.com>
… tab page surface image when the message is dismissed (#7696) Task/Issue URL: https://app.asana.com/1/137249556945/task/1212974313804281 ### Description This PR enhances the remote messaging image handling by implementing surface-specific image storage and cleanup. Now, images are stored per surface (NEW_TAB_PAGE, MODAL) and are properly cleaned up when messages are dismissed or actions are taken. ### Steps to test this PR Follow the steps in this task to test the PR: https://app.asana.com/1/137249556945/task/1213116538171640 ### UI changes No visual changes, only behavior improvements <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Medium Risk** > Touches shared remote-messaging APIs and image storage behavior, which could cause missing/stale images across surfaces if call sites aren’t updated or surface selection is incorrect. > > **Overview** > Remote message image handling is now **surface-specific**: `getRemoteMessageImageFile` takes a `Surface` and a new `clearMessageImage(surface)` API clears only that surface’s cached file. > > The image store now saves separate files per surface (e.g., `active_message_remote_image_<surface>.png`) and callers (`NewTabPageViewModel`, `RemoteMessageViewModel`, `CardsListRemoteMessageViewModel`) request/clear images for `NEW_TAB_PAGE` vs `MODAL`; new-tab flows clear the image when a message is dismissed or an action is taken, *except* for `Action.Share`. > > Tests are updated/added to cover the new surface-parameterized APIs and the Share “don’t clear image” behavior. > > <sup>Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit ec575e8. This will update automatically on new commits. Configure [here](https://cursor.com/dashboard?tab=bugbot).</sup> <!-- /CURSOR_SUMMARY -->
Task/Issue URL: https://app.asana.com/1/137249556945/task/1213116538134363 ### Description This PR makes several UI refinements to improve the appearance and consistency of remote messaging components. ### Steps to test this PR _Visual Changes_ Check that the “What’s New” components match the Figma design. ### UI changes | Phone | Tablet | | ------ | ----- | |[ship_review_feedback_phone.mp4 <span class="graphite__hidden">(uploaded via Graphite)</span> <img class="graphite__hidden" src="https://app.graphite.com/user-attachments/thumbnails/b96137c7-7e14-4fda-abb7-fb60387cd516.mp4" />](https://app.graphite.com/user-attachments/video/b96137c7-7e14-4fda-abb7-fb60387cd516.mp4)|[ship_review_feedback_tablet.mp4 <span class="graphite__hidden">(uploaded via Graphite)</span> <img class="graphite__hidden" src="https://app.graphite.com/user-attachments/thumbnails/c080a4f6-8270-44f9-92f3-e53b3bb04af5.mp4" />](https://app.graphite.com/user-attachments/video/c080a4f6-8270-44f9-92f3-e53b3bb04af5.mp4) | <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Low Risk** > Pure UI/layout and string-capitalization tweaks with no logic, data, or security impact; risk is limited to visual regressions across themes/devices. > > **Overview** > Updates the Settings label from `What's new` to `What's New`. > > Refines remote messaging “What’s New” UI styling: adjusts entry background/stroke colors and stroke width, adds/relocates padding/margins, aligns start/end icons vertically, and standardizes typography (titles/descriptions) across card, featured, and section title list items (including lowering featured card elevation and shrinking featured images). > > <sup>Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit d4d60da. This will update automatically on new commits. Configure [here](https://cursor.com/dashboard?tab=bugbot).</sup> <!-- /CURSOR_SUMMARY -->
Task/Issue URL: https://app.asana.com/1/137249556945/task/1213189507552666 ### Description Update button UI for promo message ### Steps to test this PR - [ ] Install from branch - [ ] Go to Settings > Design System Preview > Messaging - [ ] Check promo message button UI is updated ### UI changes | Before | After | | ------ | ----- | <img width="477" alt="Screenshot 2026-02-04 at 21 47 32" src="https://github.com/user-attachments/assets/c2cae10f-0d3d-4c38-8491-56409cef4d1f" />|<img width="477" height="516" alt="Screenshot 2026-02-09 at 14 35 46" src="https://github.com/user-attachments/assets/7589f99f-30f6-4c54-8a3d-f066bb6e089c" />| <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Low Risk** > Layout-only UI changes (margins and button style/icon) with no logic or data-flow impact; risk is limited to minor visual/regression issues in the promo message component. > > **Overview** > Updates the promo message CTA layout (`view_promo_message_cta.xml`) by increasing the title’s horizontal margins (from `keyline_2` to `keyline_4`). > > Switches the CTA button from `DaxButtonSecondary` to **`DaxButtonPrimary`** and adds an `ic_share_android_16` icon, updating the visual emphasis and affordance of the promo action. > > <sup>Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit 1a1ef5b. This will update automatically on new commits. Configure [here](https://cursor.com/dashboard?tab=bugbot).</sup> <!-- /CURSOR_SUMMARY -->
…l in context handling (#7701) Task/Issue URL: https://app.asana.com/1/137249556945/task/1213149350832898 ### Description This PR adds a screen name to the `ImportBookmarksViaGoogleTakeoutScreen` activity starter and fixes a bug in the `CardsListRemoteMessageView` where the `SubmitUrlInContext` command was incorrectly using the `submitUrl` method instead of its own dedicated method. The PR introduces a new `submitUrlInContext` method that uses the appropriate navigation approach for in-context URL handling. ### Steps to test this PR Follow the steps in this task to test the PR: https://app.asana.com/1/137249556945/task/1213187632702844 [whats-new-improvements.mp4 <span class="graphite__hidden">(uploaded via Graphite)</span> <img class="graphite__hidden" src="https://app.graphite.com/user-attachments/thumbnails/82a53f98-29f5-4004-8f22-43e63cce79fa.mp4" />](https://app.graphite.com/user-attachments/video/82a53f98-29f5-4004-8f22-43e63cce79fa.mp4) <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Low Risk** > Small, localized navigation changes; main risk is unintended routing differences when opening links from remote messages or launching the import activity. > > **Overview** > Adds an explicit `screenName` ("importGoogleBookmarks") to the `@ContributeToActivityStarter` registration for the Google bookmarks takeout import activity. > > Fixes remote message URL handling so `SubmitUrl` opens links in the current browser tab via `BrowserNav`, while `SubmitUrlInContext` now uses its dedicated path to launch `WebViewActivityWithParams` (instead of incorrectly reusing `submitUrl`). > > <sup>Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit b7fdc6d. This will update automatically on new commits. Configure [here](https://cursor.com/dashboard?tab=bugbot).</sup> <!-- /CURSOR_SUMMARY -->
Task/Issue URL: https://app.asana.com/0/488551667048375/1213205345136338/f ----- - Automated content scope scripts dependency update This PR updates the content scope scripts dependency to the latest available version and copies the necessary files. Tests will only run if something has changed in the `node_modules/@duckduckgo/content-scope-scripts` folder. If only the package version has changed, there is no need to run the tests. If tests have failed, see https://app.asana.com/0/1202561462274611/1203986899650836/f for further information on what to do next. _`content-scope-scripts` folder update_ - [ ] All tests must pass - [ ] Privacy tests must pass _Only `content-scope-scripts` package update_ - [ ] All tests must pass - [ ] Privacy tests do not need to run <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Low Risk** > Lockfile-only dependency bump with no application code changes; risk is limited to any upstream behavior change in `tldts`. > > **Overview** > Updates `package-lock.json` to bump `tldts-core` and `tldts-experimental` from `7.0.22` to `7.0.23`, including updated tarball URLs/integrity hashes and the `tldts-experimental` dependency on `tldts-core`. > > <sup>Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit 5944ddc. This will update automatically on new commits. Configure [here](https://cursor.com/dashboard?tab=bugbot).</sup> <!-- /CURSOR_SUMMARY --> Co-authored-by: daxmobile <daxmobile@users.noreply.github.com>
Task/Issue URL: https://app.asana.com/1/137249556945/task/1213130409431278 ### Description First part of the recent chats feature, which implements a chat suggestion list in the duck.ai tab of the input screen. Introduces a new feature flag: aiChatSuggestions. When enabled, the AI chat suggestions (pinned and recent chats) in the input screen's duck.ai mode. The UI is currently a work in progress and is using static test data. *Changes:* - Added a new overlay in the input screen fragment for the chat suggestions. This follows the same pattern as the autocomplete overlay. - Add a recycler view (with its adapter and items) to the chatSuggestionsOverlay to display the list of recent chats. - Modified how the Logo visibility is handled. The Duck.ai chat logo should no longer be visible even if there are not recent chat items. Only the search logo will be visible (Follows iOS implementation) - Added icons for the chats (pin vs chat bubble) - Added the model for the Chat suggestion. It follow the expected structure for future integration with the JS frontend. - Modified the Input Screen View Model to load the static list. - All the changes are UX/fragments, Testing will be done through Maestro. Unit tests will be implemented when real data starts flowing through the view model. *Next Steps* - Connect with the real frontend data once the JS webview interface is ready - Clicking on a recent chat should navigate you to the Duck.ai page and load the related chat. - Maestro tests ### Steps to test this PR Notes: in order to facilitate testing, I created an additional branch with static data. Please pull the `origin/feature/youssef/do_not_merge/recent_ai_chats_ui_test_data` branch to test this PR with data and see the behavior. This would prevent having test data part of the merge and allow PR testing. - Pull the changes and run the app - Go to feature flags settings and turn on "aiChatSuggestions" flag (it's off by default) - Go back to the input screen, make sure the omnibar is enabled - Switch to the "Duck.ai" tab. You should see a static list of chats, both pinned and recent. - Switch back and forth between search and chat to make sure the interaction and visibility is correct. You can test different behaviors on the search tab such as searching or adding a favorite. No impact should be observed. - If "aiChatSuggestions" is disabled, the app should behave as it is currently in production. No impact should be observed ### UI changes | Before | After | | ------ | ----- | ||| ||| <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Medium Risk** > Feature-flagged but touches core input-screen UI state/animation and overlay interaction, which can introduce subtle regressions in tab switching, visibility, and touch handling. > > **Overview** > Adds a new remote sub-feature flag, `DuckChatFeature.aiChatSuggestions`, to gate an in-progress “recent/pinned chats” suggestions experience in the Duck.ai input-screen tab. > > When enabled, the input screen now includes a new `chatSuggestionsOverlay` (RecyclerView + bottom fade/blur) and shared overlay animation logic, and updates logo/overlay/viewpager-interaction behavior to avoid showing the Duck.ai logo and to ensure autocomplete/suggestions overlays don’t overlap during mode switches. `ChatTabFragment` is expanded to wire up a `ChatSuggestionsAdapter` and observe a new `InputScreenViewModel.chatSuggestions` flow (loading stubbed via a TODO), with new pin/chat icons and an `item_chat_suggestion` row layout; tests are updated to inject the new feature toggle dependency. > > <sup>Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit 595d297. This will update automatically on new commits. Configure [here](https://cursor.com/dashboard?tab=bugbot).</sup> <!-- /CURSOR_SUMMARY --> Co-authored-by: Youssef Keyrouz <ykeyrouz@Youssefs-MacBook-Pro.local>
Task/Issue URL: https://app.asana.com/1/137249556945/task/1213085863198314 ### Description See attached task description ### Steps to test this PR https://app.asana.com/1/137249556945/task/1213176315984932 <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Medium Risk** > Touches analytics/pixel APIs and their call sites, requiring signature updates across production and tests; runtime risk is mitigated by wrapping VPN state lookup in `runCatching` and defaulting to `false`. > > **Overview** > PIR now enriches eligible scan/opt-out result pixels with a new `vpn_connection_state` parameter (mapped to `connected`/`disconnected`) for `PIR_SCAN_STAGE_RESULT_*` and opt-out submit success/failure pixels. > > `RealPirRunStateHandler` is updated to depend on `NetworkProtectionState` and safely query `isRunning()` (fail-closed) when emitting these pixels; tests and the end-to-end instrumentation setup add a `FakeNetworkProtectionState`, and `pir-impl` now depends on `:network-protection-api`. > > <sup>Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit 754362f. This will update automatically on new commits. Configure [here](https://cursor.com/dashboard?tab=bugbot).</sup> <!-- /CURSOR_SUMMARY -->
…icons. (#7710) Task/Issue URL: https://app.asana.com/1/137249556945/task/1212341818869426 ### Description Handle unreasonably large favicons gracefully, by setting a max size in pixels. ### Steps to test this PR 1. Start a local HTTP server serving a page with a large apple-touch-icon: cd /tmp/favicon_test && python3 -m http.server 8888 (The test page serves a 6708×6708 PNG as its touch icon) 2. On the device, open the DuckDuckGo browser and navigate to http://{your-server-ip}:8888 3. Wait ~2 seconds for the touch icon to be discovered and downloaded 4. Open the tab switcher 5. Without the changes: App crashes with: `RuntimeException: Canvas: trying to draw too large(179989056bytes) bitmap` But with the changes shouldn't crash. <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Low Risk** > Small, localized change that only constrains favicon bitmap dimensions; low risk aside from potential quality reduction for very large icons. > > **Overview** > Prevents crashes from *unreasonably large* favicons by introducing a `MAX_FAVICON_SIZE_PX` cap (512px) and enforcing it across Glide favicon fetches. > > Both async (`CustomTarget`) and sync (`submit`) download paths for disk and URL loads now request bitmaps at the capped size, limiting memory usage during decoding/rendering. > > <sup>Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit 91ca909. This will update automatically on new commits. Configure [here](https://cursor.com/dashboard?tab=bugbot).</sup> <!-- /CURSOR_SUMMARY -->
Task/Issue URL: https://app.asana.com/1/137249556945/task/1213076149941930 ### Description Fixes `Room` schema export by switching to the Room Gradle plugin. Several modules were using `annotationProcessorOptions` to configure the schema location, but this only works with `kapt`, not `KSP`. Since `Room` is compiled with `KSP` in these modules, schemas weren't being exported. Additionally (the original reason I was looking at this), absolute paths were being used which breaks `Gradle` caching. **Changes** - Added `androidx.room Gradle` plugin to affected modules - Replaced `annotationProcessorOptions` and `ksp { arg(...) }` with the new room `{ schemaDirectory(...) }` **Why the JSON schema changes/creation** Because many of the DB schema files were missing, and one was wrong. They've been missing for some time. 6 databases were affected: | Database | Missing Schemas | |-------------------------|---------------------| | AppDatabase | versions 50-60 | | VpnDatabase | versions 33-34 | | RemoteMessagingDatabase | version 2 | | SitePermissionsDatabase | versions 2-5 | | VoiceSearchDatabase | version 2 | | BrokenSiteDatabase | incorrect v1 schema | 2 databases were already working (`PirDatabase`, `Autofill` databases) because they used the correct `ksp { arg(...) }` syntax already. **Why this matters** - Enables Gradle build caching (absolute paths in old config broke cache keys) - Schema JSON files are now correctly exported for migration testing - Uses the officially recommended Room configuration method ### Steps to test this PR - Fresh install develop, do some stuff in the app then install this branch. Verify no problems in the upgrade <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Medium Risk** > Build config changes span many modules and regenerate schema artifacts; risk is mainly in build/cache behavior and Room migration test expectations rather than runtime logic. > > **Overview** > **Migrates Room schema export configuration to the official `androidx.room` Gradle plugin** across multiple modules (including `app`, `vpn-store`, `broken-site-store`, `remote-messaging-store`, `site-permissions-store`, `voice-search-store`, `experiments-impl`, `pir-impl`, and `autofill-impl`). This removes the old `annotationProcessorOptions`/`ksp { arg("room.schemaLocation", ...) }` approach, switches schema paths to project-relative (`$projectDir/schemas`), and adds `room { schemaDirectory(...) }` plus schema assets wiring for tests. > > Adds/updates Room exported schema JSONs for several databases (new versions for `AppDatabase`, `VpnDatabase`, `RemoteMessagingDatabase`, `SitePermissionsDatabase`, `VoiceSearchDatabase`) and corrects `BrokenSiteDatabase` v1 schema (primary key/identity hash), enabling migration testing and improving Gradle build cache reliability. > > <sup>Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit 6b0827e. Configure [here](https://cursor.com/dashboard?tab=bugbot).</sup> <!-- /CURSOR_SUMMARY --> Co-authored-by: Craig Russell <1336281+CDRussell@users.noreply.github.com>
Task/Issue URL: https://app.asana.com/1/137249556945/project/1201870266890790/task/1210936487262413?focus=true ### Description Our other browsers handle these states similarly for the most part, so we should adapt Android to match other platforms. As it stands, we get breakage reports from Android users whenever the privacy shield is showing the red warning dot, which means we get a lot of non-actionable feedback for people's router IPs and for sites we're trying to speculatively mitigate breakage on (turning off blocking to see if it reduces reports). ### Steps to test this PR - [x] Navigate to `noaprints.com` and `marvel.com` (both have protection disabled in the config) - [x] Confirm for each that you see the normal green privacy shield state for both (though if you click into the dashboard, the fact that protections are disabled is shown & the menu item shows as "enable privacy protection" - [x] Navigate to your local network address(es) and confirm that you see the globe icon rather than the UNPROTECTED state (privacy shield with a red dot) ### UI changes | Before | After | | ------ | ----- | <img width="1080" height="2400" alt="marvelOld" src="https://github.com/user-attachments/assets/695101f5-1ac0-49d0-a885-fd9efef852f8" />|<img width="1080" height="2400" alt="marvelNew" src="https://github.com/user-attachments/assets/cb880287-3e5a-4350-8c92-cce61469dd60" />| |<img width="1080" height="2400" alt="localOld" src="https://github.com/user-attachments/assets/f1a8126b-8222-4bac-b23a-6a62afc65eb8" />|<img width="1080" height="2400" alt="localNew" src="https://github.com/user-attachments/assets/0179bc49-8096-4908-bf1d-4813af3039c2" />| <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Medium Risk** > Changes how privacy/shield and leading icon states are derived (user-visible and used by reporting), gated by a remote toggle but touching core URL classification and privacy-state logic. > > **Overview** > Standardizes omnibar leading icon/shield behavior behind a new remote feature toggle `standardizedLeadingIcon`. > > When enabled, `OmnibarLayoutViewModel` shows the **Globe** icon for localhost/private-network/file URLs (instead of the privacy shield state), and `SiteMonitor.privacyProtection()` only returns **UNPROTECTED** for *user-initiated* allowlisting (not remote-config exceptions), reducing misleading red-dot states. Adds a new `Uri.isLocalUrl` helper (IPv4/IPv6 ranges + localhost, no DNS lookups) with extensive tests, and updates constructors/tests to inject the new toggle through `SiteFactoryImpl`/`SiteMonitor`. > > <sup>Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit d5bc522. This will update automatically on new commits. Configure [here](https://cursor.com/dashboard?tab=bugbot).</sup> <!-- /CURSOR_SUMMARY --> --------- Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com> Co-authored-by: Cursor Agent <cursoragent@cursor.com> Co-authored-by: Kate Manning <laghee@users.noreply.github.com>
Task/Issue URL: https://app.asana.com/1/137249556945/task/1213210525832193 ### Description On the input screen view, we are able to switch tabs (between Search and Duck.ai) using the swipe gesture. When we type something in the search box the autocomplete overlay is shown and a list of autocomplete suggestions is displayed. Swipe gesture works inside the list items but not in empty areas. ### Steps to reproduce - Go to Settings → AI Features → Make sure “Duck.ai” is turned on and “Search & Duck.ai” mode is selected. - Go back to the Input screen with the omnibar. The Search tab is selected by default - Swipe to the left anywhere on the page → Tab switches to Duck.ai tab correctly - Go back to the Search and type something in the chat box for the autocomplete list to show up - Swipe to the left inside the list → Tab switches correctly - Swipe to the left somewhere outside the list (in any empty area) → Tab does NOT switch. Swipe gesture is ignored. ### Root cause and fix When touching a list item, `onInterceptTouchEvent` receives ACTION_MOVE events (to allow the parent to steal it from the child). This is where horizontal swipe detection is currently implemented. When touching empty space below the items, no child handles ACTION_DOWN, so Android skips onInterceptTouchEvent for subsequent MOVE events and routes them directly to the recycler view's own onTouchEvent. onInterceptTouchEvent is never called for the ACTION_MOVE when no list item claimed the ACTION_DOWN. Fix: Added horizontal swipe detection in the recycler view's onTouchEvent to cover this path. If it's a move action, and the interceptor didn't already detect it, we detect it here. <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Low Risk** > Small, localized touch-handling change in a single custom `RecyclerView`; risk is limited to potential gesture-detection regressions in this view. > > **Overview** > Fixes swipe-to-switch-tabs when interacting with empty space in the autocomplete/chat suggestions overlays by adding horizontal-swipe detection to `SwipeableRecyclerView.onTouchEvent` (not just `onInterceptTouchEvent`). > > This ensures `ViewPager2` receives the gesture even when `ACTION_MOVE` events bypass interception, improving tab switching consistency during overlay display. > > <sup>Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit e425554. This will update automatically on new commits. Configure [here](https://cursor.com/dashboard?tab=bugbot).</sup> <!-- /CURSOR_SUMMARY --> Co-authored-by: Youssef Keyrouz <ykeyrouz@Youssefs-MacBook-Pro.local>
Task/Issue URL: https://app.asana.com/1/137249556945/task/1212863124420545 ### Description Adding my Asana ID to the github Asana mapping for automated task assignment to work. <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Low Risk** > Single-line update to a GitHub-to-Asana ID lookup table used by automation; no code or security-sensitive logic changes. > > **Overview** > Adds `YoussefKeyrouz` to `.github/actions/assign-release-task/github_asana_mapping.yml` so the `assign-release-task` GitHub Action can map that GitHub username to the correct Asana user ID for automated task assignment. > > <sup>Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit 192875c. This will update automatically on new commits. Configure [here](https://cursor.com/dashboard?tab=bugbot).</sup> <!-- /CURSOR_SUMMARY --> --------- Co-authored-by: Youssef Keyrouz <ykeyrouz@Youssefs-MacBook-Pro.local>
Task/Issue URL: https://app.asana.com/1/137249556945/project/488551667048375/task/1213011616457504?focus=true ### Description Replace pillIcon images in ListItem components for the new custom view where we can set text as string ### Steps to test this PR - [ ] Install from branch - [ ] Go to Settings and check everything looks good - [ ] Go to Android Design System Preview > List Items - [ ] Check all the yellow pills there look ok ### No UI changes <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Medium Risk** > Touches widely used design-system list-item views and their public XML attributes; risk is mainly UI regressions and attribute migration issues where old `showBetaPill`/`showNewPill`/enum `pillIcon` were referenced. > > **Overview** > **Replaces static “Beta/New” pill image usage in list-item components with the `DaxYellowPill` text-based custom view.** `OneLineListItem`, `TwoLineListItem`, and `SettingsListItem` now show/hide a `yellowPill` and set its label via new `pillText` when `pillIcon` is enabled, removing the old `showBetaPill`/`showNewPill` flags and the enum-based `pillIcon` resource mapping. > > Updates the associated XML layouts and design-system preview components to use `DaxYellowPill` and the new attrs, and removes the now-unused `showBetaPill` attribute from `WaitlistCheckListItem` attrs. Also tweaks constraints/minHeight in item layouts to accommodate the new pill view. > > <sup>Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit 068d064. This will update automatically on new commits. Configure [here](https://cursor.com/dashboard?tab=bugbot).</sup> <!-- /CURSOR_SUMMARY -->
Task/Issue URL: https://app.asana.com/1/137249556945/task/1213119876946268 ### Description This PR adds translations for the Duck.ai Contextual work ### Steps to test this PR Green CI <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Low Risk** > Changes are limited to Android string resources and a small wiring update to reference the new summarize string key, with minimal functional impact beyond displayed text. > > **Overview** > Adds localized `Duck.ai` contextual bottom-sheet strings (e.g., summarize prompt and page-content attachment/auto-send labels) across many `values-*/strings-duckchat.xml` locales and the default `values/strings-duckchat.xml`. > > Updates `DuckChatContextualFragment` to use the new `duckAIContextualPromptSummarize` string resource (replacing the previous `duckAIContextualSummarizePrompt`) and removes the old `values/donottranslate.xml` resource file; also includes minor test renaming and small SERP logo string metadata/text tweaks. > > <sup>Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit cc4ac8e. This will update automatically on new commits. Configure [here](https://cursor.com/dashboard?tab=bugbot).</sup> <!-- /CURSOR_SUMMARY --> --- - To see the specific tasks where the Asana app for GitHub is being used, see below: - https://app.asana.com/0/0/1213119876946268 --------- Co-authored-by: Dax The Translator <daxmobile@duckduckgo.com>
Task/Issue URL: https://app.asana.com/1/137249556945/task/1213081921435007 ### Description This PR improves the prompt replacement logic ### Steps to test this PR Enable contextualMode FF _No previous prompt_ - [x] Open contextual and tap on “Summarise This Page" - [x] Verify prompt has been replaced (no spaces before or after) _With previous prompt_ - [x] Open contextual and enter some text in the input field - [x] Tap on “Summarise This Page" - [x] Verify prompt has been added at the end of the current prompt <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Low Risk** > Changes are limited to contextual prompt UI/state handling and add/adjust unit tests; no auth, networking, or persistence logic is materially altered. > > **Overview** > Improves Duck.ai contextual *auto-prompt* behavior by changing `replacePrompt` to append the predefined “Summarize” prompt to any existing user input (or replace it when empty), and to only show page context when the cached context JSON is valid. > > Moves prompt clearing to the ViewModel via a new `onPromptCleared` (so clearing doesn’t implicitly toggle context), updates the fragment to keep the cursor at the end when restoring a prompt, and expands/updates `DuckChatContextualViewModelTest` coverage for these cases. > > <sup>Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit 66a629c. This will update automatically on new commits. Configure [here](https://cursor.com/dashboard?tab=bugbot).</sup> <!-- /CURSOR_SUMMARY -->
Task/Issue URL: https://app.asana.com/0/488551667048375/1213628236396385/f ----- - Automated content scope scripts dependency update This PR updates the content scope scripts dependency to the latest available version and copies the necessary files. Tests will only run if something has changed in the `node_modules/@duckduckgo/content-scope-scripts` folder. If only the package version has changed, there is no need to run the tests. If tests have failed, see https://app.asana.com/0/1202561462274611/1203986899650836/f for further information on what to do next. _`content-scope-scripts` folder update_ - [ ] All tests must pass - [ ] Privacy tests must pass _Only `content-scope-scripts` package update_ - [ ] All tests must pass - [ ] Privacy tests do not need to run <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Medium Risk** > Dependency update changes which content-scope features are enabled on Apple vs `apple-isolated`, and tweaks Duck Player’s environment detection logic; both could affect injected feature availability and theming at runtime. > > **Overview** > Updates `@duckduckgo/content-scope-scripts` from `13.29.0` to `13.31.0` (with updated vendored `node_modules` build artifacts). > > This shifts `webInterferenceDetection` out of the `apple` platform feature list into `apple-isolated` across the Android build bundles, changing where that feature is enabled. > > Duck Player’s bundled `EnvironmentProvider` now uses a shared `useMediaQuery` hook to track dark-mode changes (instead of bespoke listener code), while leaving reduced-motion handling as-is. `package-lock.json` also bumps `@duckduckgo/autoconsent` to `14.59.0`. > > <sup>Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit e6da1bc. This will update automatically on new commits. Configure [here](https://cursor.com/dashboard?tab=bugbot).</sup> <!-- /CURSOR_SUMMARY --> Co-authored-by: daxmobile <daxmobile@users.noreply.github.com>
Task/Issue URL: https://app.asana.com/1/137249556945/project/72649045549333/task/1213541271498408?focus=true ### Description Add menu item pixels for Duck.ai, Password, Email protection, settings and VPN. ### Steps to test this PR - [x] Be sure you can show VPN and email protection menu items in the app - [x] Run the application - [x] Enable the new browser menu from Appearance settings - [x] In new tab mode, tap on VPN menu item - [x] Check the new pixel is well emitted ``` Pixel url request: https://improving.duckduckgo.com/t/m_sheet-menu_vpn_android_phone?atb=v525-1ru&appVersion=5.270.2&test=1 ``` - [x] In browser, new tab and duck ai modes, tap on Duck.ai menu item - [x] Check the new pixel is well emitted ``` Pixel url request: https://improving.duckduckgo.com/t/m_sheet-menu_aichat_android_phone?atb=v525-1ru&appVersion=5.270.2&test=1 ``` - [x] In browser mode, tap on Generated Private Address menu item - [x] Check the new pixel is well emitted ``` Pixel url request: https://improving.duckduckgo.com/t/m_sheet-menu_new-duck-address_android_phone?atb=v525-1ru&appVersion=5.270.2&test=1 ``` - [x] In browser, new tab and Duck.ai modes, tap on Passwords menu item - [x] Check the new pixel is well emitted ``` Pixel url request: https://improving.duckduckgo.com/t/m_sheet-menu_passwords_android_phone?atb=v525-1ru&appVersion=5.270.2&test=1 ``` - [x] In brower, new tab and Duck.ai modes, tap on Settings menu item - [x] Check the new pixel is well emitted ``` Pixel url request: https://improving.duckduckgo.com/t/m_sheet-menu_settings_android_phone?atb=v525-1ru&appVersion=5.270.2&test=1 ``` ### UI changes n/a
Task/Issue URL: https://app.asana.com/1/137249556945/project/1208671518894266/task/1213611390820529?focus=true ### Description - Rewrites the README for conciseness and readability; adds a Quick Start section - Adds build variant warning (patches only work in `internal` builds) - Documents patch ordering, conflict resolution, and failure behaviour - Adds "Writing a patch file" section with examples for overriding feature state, sub-features, and adding new sub-features - Clarifies when the full patch (hash removal + version bump) is needed vs. a simple state override - Adds "Verifying a patch was applied" section covering Gradle build output and logcat - Restructures usage: local dev via `local.properties` vs. committed patches via `-Pconfig_patches` flag - Adds Maestro test file layout example and end-to-end build+test workflow - Adds GitHub Actions workflow example using `checkout-and-assemble` `gradle_flags` input - Adds PR review workflow: how to share patch files in a PR description so reviewers can verify feature flag states step by step - Adds patch examples section with full before/after config for common scenarios ### Steps to test this PR - [ ] Read through the updated README and verify accuracy - [ ] Verify the Maestro and GitHub Actions examples match existing workflow patterns in the repo 🤖 Generated with [Claude Code](https://claude.com/claude-code)
Task/Issue URL: https://app.asana.com/1/137249556945/project/72649045549333/task/1213623825931240?focus=true ### Description Add a new string for translation. This string will be used as a title in the promo onbaording dialog shown to reinstallers that skip onboarding. ### Steps to test this PR - [ ] N/A ### No UI changes <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Low Risk** > Low risk localization-only change: adds a single new string key across multiple `strings.xml` locales with no functional code changes. > > **Overview** > Adds a new localized string resource, `onboardingSkippedPrivacyProDaxDialogTitle`, across the default `values/strings.xml` and many translated `values-*/strings.xml` files. > > This enables a dedicated title (e.g., “Did you know?”) for the Privacy Pro promo onboarding dialog shown when reinstallers skip onboarding. > > <sup>Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit 61d59b1. This will update automatically on new commits. Configure [here](https://cursor.com/dashboard?tab=bugbot).</sup> <!-- /CURSOR_SUMMARY --> --------- Co-authored-by: Dax The Translator <daxmobile@duckduckgo.com>
Task/Issue URL: https://app.asana.com/1/137249556945/project/1208671518894266/task/1213612531294763 ### Description Moves git/PR workflow guidance out of `.cursor/rules/architecture.mdc` into a new dedicated `.cursor/rules/contributions.mdc` rule file covering: - Branch naming conventions - Commit message style - PR body sections (Task/Issue URL, Description, Steps to test, UI changes) Updates `AGENTS.md` to reference the new rule file. Adds `.claude/rules/contributions.md` as a symlink so Claude Code also loads the same rules. ### Steps to test this PR _Documentation change — no functional code modified_ - [ ] Confirm the git workflow section has been removed from `.cursor/rules/architecture.mdc` - [ ] Confirm `.cursor/rules/contributions.mdc` exists and contains branch naming, commit messages, and PR body guidance - [ ] Confirm `AGENTS.md` references `.cursor/rules/contributions.mdc` in the rule file table - [ ] Confirm `.claude/rules/contributions.md` symlinks to the Cursor rule file ### UI changes N/A — documentation-only change --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
…#7917) Task/Issue URL: https://app.asana.com/1/137249556945/project/1211760946270935/task/1213422200354215?focus=true ### Description Restores `release_tests.yml` to allow it to trigger release tests to run on Maestro Cloud from CI from an arbitrary commit hash ### Changes: - Add initial `develop` checkout so composite actions resolve correctly for hotfix refs - Remove `test-tag` input — always run `releaseTest` - Rename `app-version` input to `commit-ref` (accepts branches, tags, or SHAs) - Rename workflow to "Ad-hoc Release Tests - Maestro Cloud" - Remove Asana task creation on failure ### Steps to test this PR - QA optional - Tested in runs https://github.com/duckduckgo/Android/actions/runs/22862027726 and https://github.com/duckduckgo/Android/actions/runs/22861749518 to run against a tag and a branch <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Low Risk** > Low risk: changes are limited to a manually-triggered CI workflow, though it affects how release-test runs are checked out and could break ad-hoc testing if refs/actions resolution is wrong. > > **Overview** > Restores/streamlines the ad-hoc Maestro Cloud release testing workflow so it can be run against an arbitrary branch/tag/SHA via a renamed `commit-ref` input. > > The workflow now checks out `develop` first to ensure local composite actions resolve, always runs the `releaseTest` Maestro tag (removing the configurable `test-tag` input), and removes the Asana task creation and related env vars on failure. > > <sup>Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit 76c5545. Configure [here](https://cursor.com/dashboard?tab=bugbot).</sup> <!-- /CURSOR_SUMMARY --> Co-authored-by: Craig Russell <1336281+CDRussell@users.noreply.github.com>
Task/Issue URL: https://app.asana.com/1/137249556945/project/72649045549333/task/1210557489696946?focus=true ### Description Show subscription dialog 7 days after install if onboarding is skipped ### Steps to test this PR _Pre steps_ - [x] Apply patch on https://app.asana.com/1/137249556945/project/1209991789468715/task/1210448620621729?focus=true _Subscription dialog for users that skip onboarding_ - [x] Fresh install - [x] Skip onboarding as a returning user (Skip onboarding as a returning user (I've been here before > Start Browsing) - [x] Close the app - [x] Change the date in your device for >=7 days after today - [x] Open the app and check subscription onboarding dialog appear (probably subscribe copy as you already used a Free Trial in the past) _Free Trial copy_ - [x] On Play Billing Lab app go to Configuration settings and check 'Test free trial or introductory offer' - [x] Remember to change date back to today - [x] Fresh install - [x] Skip onboarding as a returning user (not with dev button) - [x] Close the app - [x] Change the date in your device for >=7 days after today - [x] Open the app and check subscription onboarding dialog appear with Free Trial copy _FF disabled_ - [x] Fresh install - [x] Remember to change date back to today - [x] Skip onboarding as a returning user (not with dev button) - [x] Go to Settings > Feature Flag Inventory - [x] Disable `privacyProCtaSkippedOnboarding` - [x] Close the app - [x] Change the date in your device for >=7 days after today - [x] Open the app and check subscription onboarding dialog doesn't appear ### UI changes | Before | After | | ------ | ----- | !(Upload before screenshot)|(Upload after screenshot)| <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Medium Risk** > Changes onboarding CTA selection and command emission timing, which can affect when promos/omnibar input auto-launch and subscription URLs are triggered. Risk is moderate due to new time-based gating, new feature flags, and updated pixel parameters, but it’s isolated to onboarding/promo flows. > > **Overview** > Adds a new *Privacy Pro onboarding promo* path for returning users who previously skipped onboarding: after **7 days since install**, a `DaxPrivacyProCta` can be returned from `CtaViewModel.refreshCta` when `privacyProCtaSkippedOnboarding` is enabled. > > Updates the Privacy Pro CTA model to derive title/CTA copy from `onboardingSkipped` and free-trial eligibility, avoid auto-marking the CTA as read on show, and include new pixel params (`ru`, `free_trial`) for shown/OK events. Privacy Pro launch now builds the `origin` query parameter dynamically via `getPrivacyProOnboardingOrigin()`. > > Prevents Duck.ai’s `LaunchInputScreen` auto-open on new empty tabs when the skipped-onboarding promo is eligible to be shown, reducing UI conflicts. Feature toggle defaults are adjusted (`extendedOnboarding.self` and `privacyProCta` default to true; new `privacyProCtaSkippedOnboarding` added), and tests are updated/added to cover the new gating and suppression behavior. > > <sup>Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit 9d4e7cf. This will update automatically on new commits. Configure [here](https://cursor.com/dashboard?tab=bugbot).</sup> <!-- /CURSOR_SUMMARY -->
Task/Issue URL: https://app.asana.com/1/137249556945/project/1200204095367872/task/1212305800759148?focus=true ### Description - Updates the the unfocussed native input UI ### Steps to test this PR Enable feature flag > “nativeInputField", AI Features > “Native Input Field” - [x] While the widget is showing, hide the keyboard - [x] Verify that the widget contracts and the omnibar tabs + menu button are visible - [x] Focus the widget - [x] Verify that the widget fills the width and the buttons are hidden - [x] Repeat for bottom and split configurations <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Medium Risk** > Moderate UI behavior change that alters omnibar/widget layering, margins, and button visibility based on keyboard and mode; risk is primarily visual regressions across bottom/split/AI configurations. > > **Overview** > Implements an *unfocused native input* state: when the keyboard hides (non-DuckAI, non-split), the native input widget contracts and the omnibar `tabs` + `menu` buttons are shown again, with updated z-ordering and end-margin sizing based on measured button widths. > > Refactors native input/omnibar coordination by adding split-mode awareness, extracting omnibar transparency/content-hiding into reusable helpers with a managed layout listener, and updating widget card styling (new fully-rounded shape + compat padding) plus dynamic card margins/translationZ to prevent overlap. > > <sup>Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit 2deccbd. This will update automatically on new commits. Configure [here](https://cursor.com/dashboard?tab=bugbot).</sup> <!-- /CURSOR_SUMMARY -->
Task/Issue URL: https://app.asana.com/1/137249556945/project/1157893581871903/task/1212838292837873 ### Description Replaces individual one-off Maestro workflows with a generic reusable workflow, now that binary ID reuse and the flow analyser composite action are in place. - Added `.github/workflows/e2e-maestro.yml` — a generic workflow callable via `workflow_call` or `workflow_dispatch` with inputs: `tags`, `test_name`, `release_blocker` (default `false`), `binary_id` (optional, skips assembly), `app_flavour` (`play`/`internal`, default `play`) - Added omnibar and security-internal test suites to `e2e-nightly-full-suite.yml` (not release blocking) - Deleted 6 individual workflows now redundant: - `e2e-nightly-custom-tabs.yml` — already covered by full suite - `e2e-nightly-android-design-system.yml` - `e2e-nightly-input-screen.yml` - `e2e-nightly-omnibar.yml` — moved to full suite - `e2e-security.yml` — already covered by full suite - `e2e-security-internal.yml` — moved to full suite Kept as-is (special cases): autofill (custom gradle flag), sync critical path (account pre-step), duckplayer and privacy-dashboard (PR path triggers). ### Steps to test this PR - [ ] Trigger `e2e-maestro.yml` manually via workflow_dispatch with `tags: omnibarTest`, `test_name: Omnibar`, `app_flavour: play` and verify it runs correctly - [ ] Trigger again with a known `binary_id` and verify the assembly step is skipped - [ ] Confirm the 6 deleted workflows no longer appear in the Actions UI - [ ] Confirm `nightly-orchestrator.yml` still runs successfully ### UI changes N/A <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Medium Risk** > Refactors GitHub Actions E2E orchestration and scheduling, which can change what runs (and when) and how failures are reported. Main risk is missed coverage or missing Asana tasks due to new gating/defaults. > > **Overview** > Introduces a reusable generic Maestro E2E workflow (`e2e-maestro.yml`) to run tagged suites via `workflow_call`/`workflow_dispatch`, standardizing assembly + Maestro execution. > > Cleans up CI by removing several one-off Maestro workflows and replacing nightly coverage with a new consolidated *non-release-blocker* nightly suite (`e2e-nightly-non-blockers-suite.yml`), while also renaming the failure Asana task in `e2e-nightly-full-suite.yml`. > > Updates the `maestro-cloud-asana-reporter` composite action to support optionally skipping Asana error-task creation (`create_asana_error_task`), and adjusts `e2e-duckplayer.yml` to no longer run on a nightly schedule. > > <sup>Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit bb1ff25. This will update automatically on new commits. Configure [here](https://cursor.com/dashboard?tab=bugbot).</sup> <!-- /CURSOR_SUMMARY --> --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Task/Issue URL: https://app.asana.com/1/137249556945/project/1211724162604201/task/1211938369145726?focus=true ### Description Adds comprehensive reference tests for the Request Blocklist feature to validate blocking behavior against standardized test cases. The test suite covers basic blocking functionality, allowlisting interactions, tracker detection integration, incorrect rule handling, and rule ordering scenarios. ### Steps to test this PR _Request Blocklist Reference Tests_ - [ ] Run the new `RequestBlocklistReferenceTest` to verify all test cases pass - [x] CI pases ### UI changes No UI changes <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Low Risk** > Test-only changes that add a new instrumented reference test suite and accompanying fixtures; no production logic is modified. > > **Overview** > Adds a new Android instrumented, parameterized reference test (`RequestBlocklistReferenceTest`) that exercises `WebViewRequestInterceptor` request-blocklist decisions against the shared privacy reference test corpus. > > Introduces new `androidTest` fixtures (`tests.json`, `config-reference.json`, `tds-reference.json`, `surrogates-reference.txt`, `user-allowlist-reference.json`) and updates `copy-files-from-to.json` to sync these files from `@duckduckgo/privacy-reference-tests`. > > <sup>Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit 89face1. This will update automatically on new commits. Configure [here](https://cursor.com/dashboard?tab=bugbot).</sup> <!-- /CURSOR_SUMMARY -->
Task/Issue URL: https://app.asana.com/1/137249556945/project/1207418217763355/task/1213613093641078?focus=true ### Description The chats [have been migrated from local storage to IndexedDB](https://app.asana.com/1/137249556945/project/72649045549333/task/1212751456413961). The problem is that the existing logic to notify the sync about the deletion relied on the chats being cleared from local storage, which means that this no longer happened. This PR makes the following changes: - Updates the sync notification so that it happens when chats are cleared from IndexedDB - Removes the chat data clearing from local storage, as it doesn't exist there anymore - Removes `DataClearingFlowStep.WEB_STORAGE_CLEAR_GRANULAR` step from the wide event, because it's not needed anymore ### Steps to test this PR - [ ] [Enable sync on 2 devices](https://app.asana.com/1/137249556945/project/72649045549333/task/1212472824864986?focus=true) - [ ] Create a couple of chats - [ ] Clear all data (including Duck.ai chats) on 1 device - [ ] Verify the chats are cleared on all devices <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Medium Risk** > Changes data-clearing behavior and sync-deletion notification timing by moving chat-deletion signaling to IndexedDB clears; issues could leave chat deletion timestamps unsynced or trigger extra notifications. Logic is still bounded to data-clearing flows and covered by updated tests. > > **Overview** > Fixes Duck AI chat clearing with sync by **notifying `DuckAiChatDeletionListener` when Duck AI data is cleared from IndexedDB**, both for legacy full clears and granular clears. > > Simplifies web storage clearing by removing the granular WebStorage clear path and related wide-event step/pixel parameters, and by stripping Duck AI–specific deletion logic and config (`keysToDelete`) from `WebLocalStorageManager`/settings parsing. > > Updates instrumentation/unit tests to match the new clearing semantics and to assert correct notification behavior (including IndexedDB failure cases). > > <sup>Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit f704e8a. This will update automatically on new commits. Configure [here](https://cursor.com/dashboard?tab=bugbot).</sup> <!-- /CURSOR_SUMMARY --> --------- Co-authored-by: Cursor Agent <cursoragent@cursor.com> Co-authored-by: cursor[bot] <206951365+cursor[bot]@users.noreply.github.com>
Task/Issue URL: https://app.asana.com/1/137249556945/project/1212810093780571/task/1213484200004107?focus=true ### Description Add the @tab attqachment feature to the input screen chat box (when you toggle to Duck.ai). We'll use it as a starting point while the design is being defined. This will allow us to hookup all the plumbing and behind-the-scene logic without getting blocked by the UI design. Everything is behind the feature flag `chatTabAttachments` which is Disabled by default. Main features: - @ tagging in the Duck.ai chat field - Filtering and deleting tags - Tag submission with the query. Currenlty only title and URL are submitted Out of scope: Attaching and submitting the tab content in the query. This will be collected and attached in a later task. ### Steps to test - Pull the changes - Enable feature flag `chatTabAttachments` - Open few tabs in your browser - Type @ in the chat box to see the list of currently open tabs - Type after the @ to filter on specific tabs - Select a tab to add it to the input field. - Add multiple tabs - Delete the whole tag by only clicking backspace once - Can test edge cases with @ usage that is not a tab or manipulating the input field through autocorrect or other shortcuts. ### UI changes | Before | After | | ------ | ------ | |<img width="540" height="1212" alt="Screenshot_20260306_002139" src="https://github.com/user-attachments/assets/151f7bc3-efd7-47bb-be52-69f8211e1b75" />|<img width="540" height="1212" alt="Screenshot_20260306_002139" src="https://github.com/user-attachments/assets/2302de60-d85d-4548-a1e8-06578501228f" />| |-|<img width="540" height="1212" alt="Screenshot_20260306_002139" src="https://github.com/user-attachments/assets/b947b913-ccbf-4d4a-8872-8b03663e71e1" />| |-|<img width="540" height="1212" alt="Screenshot_20260306_002200" src="https://github.com/user-attachments/assets/84fea595-893c-49b5-98d9-2eded0181e66" />| ### Video Demo https://github.com/user-attachments/assets/e2d64dc6-5008-47f0-86e6-abbf58080729 <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Medium Risk** > Adds new input parsing/UI state and alters chat submission to optionally append attached tab metadata; bugs could affect query text and input behavior, though guarded by a disabled-by-default feature flag. > > **Overview** > Introduces a new `chatTabAttachments` remote feature flag (default off) to enable @-mentioning open tabs from the Duck.ai input screen. > > When enabled, typing `@` in chat triggers a popup list of open tabs (with favicons) filtered by the typed query; selecting a tab inserts a styled tag span into the input, tracks attachments, and removes attachments when tags are deleted or corrupted. > > On chat submit, the view model enriches the prompt by appending an `[Attached tabs: title (url)]` block and then clears attachment state; adds UI/layout resources and unit tests covering detection, filtering, selection, removal, and enrichment behavior. > > <sup>Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit 82509f7. This will update automatically on new commits. Configure [here](https://cursor.com/dashboard?tab=bugbot).</sup> <!-- /CURSOR_SUMMARY -->
Task/Issue URL: https://app.asana.com/1/137249556945/project/1211724162604201/task/1211938369145727?focus=true ### Description This PR adds the integration test for the request blocklist. The test runs on https://privacy-test-pages.site/privacy-protections/request-blocklist/ and checks that all the necessary requests are blocked/ loaded. ### Steps to test this PR - [x] CI passes - [x] RequestBlocklistTest passes ### UI changes None. <!-- CURSOR_SUMMARY --> --- > [!NOTE] > <sup>[Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) is generating a summary for commit 04e9941. Configure [here](https://cursor.com/dashboard?tab=bugbot).</sup> <!-- /CURSOR_SUMMARY -->
Task/Issue URL: https://app.asana.com/1/137249556945/task/1212827841082637 ### Description Adds support for adwall (aggregate) counting pixels via web telemetry. ### Steps to test this PR Testing steps are covered in https://github.com/duckduckgo/ddg-workflow/blob/gd-detector-telemetry/technical-designs/web-detection-framework/eventhub-android-testing-plan.md <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Medium Risk** > Adds a new backgrounded/foregrounded lifecycle-driven telemetry pipeline that persists state in Room and fires pixels on timers, which could impact analytics correctness and scheduling behavior. The feature is gated by a remote toggle but touches WebView messaging and app lifecycle hooks. > > **Overview** > Introduces a new **`eventHub` telemetry feature** that ingests `webEvents`/`webEvent` messages from Content Scope Scripts, aggregates counter-based metrics over configurable periods, and fires bucketed pixels with an `attributionPeriod` parameter. > > Adds a new `event-hub-impl` module with remote-config parsing, per-pixel state persistence (Room), deduping per `webViewId`/navigation, scheduling to fire at period end, and plugins/hooks for privacy-config updates and app lifecycle foreground/background transitions. Updates content-scope messaging to inject `nativeData.webViewId` into `webEvent` messages, wires the module into `app/build.gradle`, and adds pixel definitions for daily/weekly adwall detection telemetry. > > <sup>Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit d880c78. This will update automatically on new commits. Configure [here](https://cursor.com/dashboard?tab=bugbot).</sup> <!-- /CURSOR_SUMMARY --> --------- Co-authored-by: Cursor Agent <cursoragent@cursor.com>
…ixes (#7872) Task/Issue URL: https://app.asana.com/1/137249556945/project/1198194956794324/task/1213548840330684?focus=true ### Description Three fixes to `GlobalActivityStarter`: **1. `startForResult()` overloads** The previous pattern for launching an activity for result was `startIntent() + launcher.launch(intent)`. `startIntent()` returns `Intent?` — passing `null` to `launcher.launch()` crashes at runtime with no clear error. The new `startForResult(context, params, launcher)` overloads resolve this and make the intent clearer at the call site. Before: ```kotlin val intent = globalActivityStarter.startIntent(this, SomeActivityParams) someLauncher.launch(intent) // crashes if intent is null ``` After: ```kotlin globalActivityStarter.startForResult(this, SomeActivityParams, someLauncher) ``` **2. Automatic `FLAG_ACTIVITY_NEW_TASK` for non-Activity contexts** `startIntent()` and `start()` now automatically add `FLAG_ACTIVITY_NEW_TASK` when `context !is Activity`. Callers in Services, broadcast receivers, and JS message handlers no longer need to add the flag manually after calling `startIntent()`. Before: ```kotlin val intent = globalActivityStarter.startIntent(context, DuckChatNativeSettingsNoParams) intent?.flags = Intent.FLAG_ACTIVITY_NEW_TASK context.startActivity(intent) ``` After: ```kotlin globalActivityStarter.start(context, DuckChatNativeSettingsNoParams) ``` **3. Defensive logging** - `logcat(ERROR)` is emitted before `IllegalArgumentException` when no mapper is found for a params type, making registration bugs visible in logcat before the crash. - `logcat(WARN)` is emitted when multiple mappers claim the same params type (previously the first match silently won with no diagnostic output). Updated callers: `RestoreSubscriptionActivity` (migrated to `startForResult`), `OpenNativeSettingsHandler` (removed manual flag), `BookmarksActivity`/`BookmarksViewModel` (removed unused `LaunchSyncSettings` + `syncActivityLauncher` — the result callback only re-ran promotion eligibility, which already happens via other paths). ### Steps to test this PR - [ ] Launch an activity for result using `startForResult()` — confirm it launches correctly and the result callback fires - [ ] Restore a subscription via **Settings → Subscription → Restore** — confirm the restore flow launches and completes without a crash - [ ] Open a Duck.ai chat, trigger the native settings from the SERP settings JS handler — confirm it opens without a crash and without needing `FLAG_ACTIVITY_NEW_TASK` manually - [ ] Confirm no regression in Bookmarks — open bookmarks, verify sync promotion behaviour is unchanged - [ ] Run `./gradlew :navigation-impl:testDebugUnitTest` — all tests should pass --- > [!NOTE] > **Medium Risk** > Touches shared navigation infrastructure used across modules; intent flagging and mapper selection changes could affect activity launches if edge cases exist, though covered by new unit tests. > > **Overview** > Improves `GlobalActivityStarter` to be safer and more ergonomic: adds `startForResult(...)` overloads for `ActivityResultLauncher`, and centralizes intent construction with clearer error logging when no mapper is found. > > `start()`/`startIntent()` now automatically apply `FLAG_ACTIVITY_NEW_TASK` for non-`Activity` contexts, and log a warning when multiple mappers match the same params (first match still wins). Call sites are updated to use the new APIs (e.g. subscriptions restore flow and SERP native-settings handler), and bookmarks removes a now-unused sync-settings launch path. > > <sup>Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit 8d5ce61. This will update automatically on new commits. Configure [here](https://cursor.com/dashboard?tab=bugbot).</sup>
Task/Issue URL: https://app.asana.com/1/137249556945/project/72649045549333/task/1213377943508982?focus=true ### Description Updates privacy pro references inside PIR ### Steps to test this PR N/A ### UI changes | Before | After | | ------ | ----- | !(Upload before screenshot)|(Upload after screenshot)| <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Low Risk** > String-only changes across many locales, plus an Android manifest label tweak for `PirActivity`; risk is limited to UI text/regional resource correctness and potential missing/unused string references. > > **Overview** > Updates localized UI copy to shift messaging from *Privacy Pro* to *DuckDuckGo Subscription* in the Personal Information Removal (PIR) flow, including changing `PirActivity`’s manifest label to `@string/ddg_subscription`. > > Adds new (currently English, `translatable="false"`) onboarding and Duck.ai input-screen strings across multiple locales (e.g., Duck.ai-specific pre-onboarding titles/buttons, comparison-chart item, and updated “search vs Duck.ai” preference labels). > > <sup>Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit 764f910. This will update automatically on new commits. Configure [here](https://cursor.com/dashboard?tab=bugbot).</sup> <!-- /CURSOR_SUMMARY --> --------- Co-authored-by: Dax The Translator <daxmobile@duckduckgo.com>
Task/Issue URL: https://app.asana.com/1/137249556945/project/1202552961248957/task/1213634576464400 ### Description The `MetricsPixelNumericValueDetector` lint rule was silently missing violations when `MetricsPixel` was used in modules where the class is loaded from a compiled dependency JAR — the constructor `PsiMethod` can't be resolved in that case, so `visitConstructor` never fired. This PR adds a fallback detection path via `getApplicableUastTypes`/`createUastHandler` that triggers on all `UCallExpression` nodes and uses source text matching + return type filtering to identify `MetricsPixel` calls. Named-argument lookup was also reworked to use `UNamedExpression` with a Kotlin PSI fallback, making it robust regardless of argument order. All tests now skip `TestMode.REORDER_ARGUMENTS` to suppress a known lint test infrastructure warning caused by overlapping edits when positional args are nested inside outer named args. ### Steps to test this PR _MetricsPixelNumericValueDetector_ - [x] Change `SearchMetricPixelsPlugin` so one of them has a value that's not a number, i.e. "test" - [x] Run `./gradlew :feature-toggles-impl:lint` - [x] Lint should throw an error ### UI changes | Before | After | | ------ | ----- | !(Upload before screenshot)|(Upload after screenshot)| <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Medium Risk** > Medium risk because it changes lint detection logic to rely on UAST/PSI fallbacks and source-text matching, which could introduce false positives/negatives across Kotlin call shapes. > > **Overview** > Fixes `MetricsPixelNumericValueDetector` missing violations when `MetricsPixel` comes from a compiled dependency by adding a fallback scan over all `UCallExpression`s and filtering to `MetricsPixel` calls when the constructor can’t be resolved. > > Reworks argument extraction to be robust to named/out-of-order arguments by using `UNamedExpression` with a Kotlin PSI fallback, and improves error location selection by reporting on the `value` expression when possible. > > Updates tests to reflect the real `MetricsPixel` signature (default `type`), adds coverage for out-of-order named arguments, and skips `TestMode.REORDER_ARGUMENTS` to avoid test infra issues. > > <sup>Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit 693bca6. This will update automatically on new commits. Configure [here](https://cursor.com/dashboard?tab=bugbot).</sup> <!-- /CURSOR_SUMMARY -->
Task/Issue URL: https://app.asana.com/1/137249556945/project/72649045549333/task/1213641986703613?focus=true ### Description Introduces new FF to enabled/disable pro entitlements fetcher Decouples that logic from the actual feature ### Steps to test this PR _Feature 1_ - [x] Apply staging patch from https://app.asana.com/1/137249556945/project/1209991789468715/task/1210448620621729?focus=true - [x] fresh install - [x] Skip onboarding - [x] Subscription settings are visible - [x] Enter purchase flow -> ensure you see "See all Plans" - [x] don't continue, navigate back - [x] Go to feature flags inventory and disable `allowProTierPuchase` - [x] Go back to settings and enter purchase flow -> should only allow plus plans ### UI changes | Before | After | | ------ | ----- | !(Upload before screenshot)|(Upload after screenshot)| <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Medium Risk** > Changes subscription feature/entitlement fetching logic at app startup to be controlled by a new remote flag, which could affect which base plans are queried and cached. Impact is limited in scope but touches subscription availability behavior. > > **Overview** > Decouples pro-tier entitlements refresh from purchase availability by introducing a new `privacyPro.fetchProTierEntitlements` remote flag (default **enabled**). > > `SubscriptionFeaturesFetcher` now uses this new flag (instead of `allowProTierPurchase`) to decide whether to include both Basic and Advanced subscription products when selecting base plans to fetch and cache subscription features/entitlements. > > <sup>Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit 5d1bab7. This will update automatically on new commits. Configure [here](https://cursor.com/dashboard?tab=bugbot).</sup> <!-- /CURSOR_SUMMARY -->
Task/Issue URL: https://app.asana.com/1/137249556945/project/72649045549333/task/1213623825931245?focus=true ### Description Add condition to show promo onboarding as soon as it is available in new tab page ### Steps to test this PR - [ ] Apply patch pinned on https://app.asana.com/1/137249556945/project/1209991789468715/task/1210448620621729?focus=true _Toggle visible_ - [x] Fresh install - [x] Skip onboarding tapping on "I've been here before" > "Start browsing" - [x] Close/open the app until you see Subscription option on Settings screen - [x] Check duck ai toggle is enabled and visible - [x] Background the app and set the date to 7 days from today. - [x] Go back to app - [x] Duck.ai toggle is visible with new tab page without the onboarding promo dialog - [x] Open a new tab - [x] Check the onboarding dialog shows correctly _Toggle no visible_ - [x] Fresh install - [x] Skip onboarding tapping on "I've been here before" > "Start browsing" - [x] Close/open the app until you see Subscription option on Settings screen - [x] Check duck ai toggle is enabled but not visible (only full screen new tab page) - [x] Background the app and set the date to 7 days from today. - [x] Go back to app - [x] Check duck.ai toggle is not launched and the onboarding dialog shows correctly ### No UI changes <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Low Risk** > Low-risk logic change that only gates the New Tab Page onboarding-complete flag when the promo onboarding dialog is eligible to show; main risk is unintended onboarding/promo dialog visibility changes. > > **Overview** > Updates `BrowserTabViewModel.refreshCta` so `isOnboardingCompleteInNewTabPage` is **false** while the promo onboarding dialog is showing/eligible, allowing the New Tab Page to display the promo onboarding UI. > > Adds a unit test covering the scenario where the Privacy Pro promo CTA is returned and the onboarding-complete flag must remain unset. > > <sup>Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit a8442f9. This will update automatically on new commits. Configure [here](https://cursor.com/dashboard?tab=bugbot).</sup> <!-- /CURSOR_SUMMARY -->
Task/Issue URL: https://app.asana.com/1/137249556945/project/1202552961248957/task/1213647284656734 ### Description When the `uiLockChanged` JS callback fires, the `locked` value should be forced to `false` if the current URL is from `duck.ai`. This is a temporary fix to avoid the omnibar being hidden in some scenarios. ### Steps to test this PR _Browser UI Lock — duck.ai behaviour_ - [ ] Open a duck.ai page and trigger a `uiLockChanged` JS callback with `locked: true` — verify the UI lock does **not** activate - [ ] Open a non-duck.ai page and trigger a `uiLockChanged` JS callback with `locked: true` — verify the UI lock **does** activate - [ ] Verify that disabling the `browserUiLock` feature flag prevents the command from being issued entirely |(Upload before screenshot)|(Upload after screenshot)| <!-- CURSOR_SUMMARY --> --- > [!NOTE] > <sup>[Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) is generating a summary for commit 18e3f23. Configure [here](https://cursor.com/dashboard?tab=bugbot).</sup> <!-- /CURSOR_SUMMARY -->
- Automated content scope scripts dependency update This PR updates the content scope scripts dependency to the latest available version and copies the necessary files. Tests will only run if something has changed in the `node_modules/@duckduckgo/content-scope-scripts` folder. If only the package version has changed, there is no need to run the tests. If tests have failed, see https://app.asana.com/0/1202561462274611/1203986899650836/f for further information on what to do next. _`content-scope-scripts` folder update_ - [x] All tests must pass - [x] Privacy tests must pass _Only `content-scope-scripts` package update_ - [ ] All tests must pass - [ ] Privacy tests do not need to run Co-authored-by: daxmobile <daxmobile@users.noreply.github.com>
Task/Issue URL: https://app.asana.com/1/137249556945/project/72649045549333/task/1213634152051167?focus=true ### Description See attached task description ### Steps to test this PR Smoke test PIR broker json download <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Medium Risk** > Changes the broker-data download/extraction pipeline and adds ZIP path validation; issues here could prevent broker updates or drop files unexpectedly, but impact is contained to PIR updates. > > **Overview** > **Hardens PIR broker JSON updates** by downloading the broker ZIP to a temp file in `cacheDir`, extracting only `.json` entries, and always cleaning up temp/extract folders. > > **Improves safety and robustness**: adds ZIP-slip protection via canonical-path checks, reuses a single lazy Moshi `brokerAdapter`, streams JSON parsing with okio instead of `readText()`, and ensures coroutine cancellation is rethrown. Adds a unit test verifying malicious ZIP entries are skipped and updates existing tests to provide `cacheDir`. > > <sup>Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit 31d1c20. This will update automatically on new commits. Configure [here](https://cursor.com/dashboard?tab=bugbot).</sup> <!-- /CURSOR_SUMMARY -->
Task/Issue URL: https://app.asana.com/1/137249556945/task/1213610121584461 ### Description When a user accepts Duck.ai terms and conditions and had already accepted them previously, fire a pixel to track this duplicate acceptance. Two separate pixels are sent depending on whether Sync is enabled: - `m_aichat_terms_accepted_duplicate_sync_on` - `m_aichat_terms_accepted_duplicate_sync_off` A new `DuckChatTermsOfServiceHandler` class encapsulates this logic, keeping it out of the already-large `RealDuckChatJSHelper`. The acceptance state is persisted in DataStore via a new `DUCK_AI_TERMS_ACCEPTED` boolean key. ### Steps to test this PR _Duplicate T&C acceptance pixel_ - [x] Open Duck.ai and accept terms and conditions - [ ] Close and reopen Duck.ai, accept terms again - [ ] Verify the appropriate pixel is fired (`sync_on` or `sync_off` depending on Sync state) - [ ] Verify first-time acceptance does not fire a pixel ### UI changes None <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Low Risk** > Low risk: adds a new persisted boolean flag and additional pixel firing paths without changing core chat or sync behavior. > > **Overview** > Adds a new `USER_DID_ACCEPT_TERMS_AND_CONDITIONS` report metric and JS messaging method (`userDidAcceptTermsAndConditions`) to signal when the user accepts Duck.ai terms. > > On terms acceptance, `RealDuckChatPixels` now persists a `DUCK_AI_TERMS_ACCEPTED` flag and fires a base acceptance pixel, plus an additional **duplicate-acceptance** pixel when the flag was already set (`DUCK_CHAT_TERMS_ACCEPTED_DUPLICATE_SYNC_ON` / `_SYNC_OFF` depending on Sync state) via a new `DuckChatTermsOfServiceHandler` abstraction. > > Extends datastore and unit tests to cover the new persistence and pixel behavior. > > <sup>Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit 5fbdeb6. This will update automatically on new commits. Configure [here](https://cursor.com/dashboard?tab=bugbot).</sup> <!-- /CURSOR_SUMMARY --> --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> Co-authored-by: Josh Leibstein <joshliebe@gmail.com>
Task/Issue URL: https://app.asana.com/1/137249556945/project/1212015278241917/task/1211670072973939?focus=true ### Description Create DaxPageHeader component in Compose for settings screens. ### Steps to test this PR _Check DaxPageHeader_ - [ ] Open the application - [ ] Go to the ADS screens from developer settings - [ ] Open the "Templates" tab - [ ] Check PageHeaders component follow our actual XML implementation ### UI changes <img width="270" height="600" alt="image" src="https://github.com/user-attachments/assets/01b4ea73-fafe-4721-a9cd-4ef47e946e38" />
Task/Issue URL: https://app.asana.com/1/137249556945/project/1198194956794324/task/1213651965660200?focus=true ### Description Updates `url-predictor-android` from `0.3.13` to `0.3.15`. This version includes a fix for embedded newlines and tab characters in omnibar input being misclassified as a Navigate decision (triggering navigation) instead of a Search decision (triggering a search query). ### Steps to test this PR _Test_ - [x] In toggle input screen, select duck.ai input - [x] Input `https://example.com` + newline + `test` into the omnibar. It should open a DDG search, **not** navigate to example.com - [x] Paste text with an embedded newline where the URL comes second, e.g. `hello world` on line 1 and `https://example.com` on line 2 — should open a DDG search, not navigate - [ ] Paste text with a tab character followed by something URL-like, e.g. a tab then `example.com` — should open a DDG search, not navigate _Regression — normal navigation still works_ - [x] Type a plain URL in the omnibar (e.g. `duckduckgo.com`) and confirm it navigates to the site - [x] Type a full URL with scheme (e.g. `https://wikipedia.org`) and confirm it navigates directly - [x] Type a search query (e.g. `how do penguins sleep`) and confirm it opens a DDG search results page ### UI changes N/A — no UI changes.
Task/Issue URL: https://app.asana.com/1/137249556945/project/1211654189969294/task/1213651299034811 ### Description Adds a mic button on the duck.ai tab of the input screen that opens duck.ai directly in voice mode (`duck.ai/?mode=voice-mode`), giving users 1-click access to voice chat. - New `duckAiVoiceEntryPoint` sub-feature flag (`DefaultFeatureValue.INTERNAL` — off in production, on in internal builds) - New `openVoiceDuckChat()` on the `DuckChat` API → implemented in `RealDuckChat` by appending `mode=voice-mode` and forcing a new session - `InputScreenViewModel`: extended the voice button visibility `combine` block to be tab-aware; duck.ai tab + flag on → button follows `voiceInputAllowed` (text presence) rather than `VoiceSearchAvailability` - `InputScreenFragment`: both voice click handlers route to `viewModel.onVoiceEntryTapped()` on the duck.ai tab when flag is on - Pixel: `m_aichat_voice_entry_tapped` fired on tap - 7 new unit tests in `InputScreenViewModelTest` ### Steps to test this PR _Enable flag_ - [x] In internal build, enable `duckAiVoiceEntryPoint` under duck.ai feature flags _Duck.ai tab — empty field_ - [x] Open the input screen and switch to the duck.ai tab - [x] Mic button is visible (even if private voice search is disabled in settings) - [x] Tap the mic button → duck.ai opens with `?mode=voice-mode` in the URL, fresh session _Duck.ai tab — with text_ - [x] Type something in the input field → mic button disappears, send button appears - [x] Clear the field → mic button reappears _Search tab (unchanged)_ - [x] Switch to the search tab → mic button follows voice search availability as before - [x] Tap mic → system voice recognition launches (not duck.ai voice mode) _Flag off_ - [x] Disable `duckAiVoiceEntryPoint` → duck.ai tab mic button behaves as before (follows voice search availability, launches system voice recognition) ### UI changes | Before | After | | ------ | ----- | |(Upload before screenshot)|(Upload after screenshot)| --- > [!NOTE] > **Medium Risk** > Changes input-screen voice button behavior behind a new feature flag and adds a new DuckChat navigation path that forces a fresh session; risk is moderate due to UI/flow branching and URL/session handling. > > **Overview** > Adds a new, flag-gated *Duck.ai voice entry point* on the input screen: when on the Duck.ai tab and `duckAiVoiceEntryPoint` is enabled, the mic button opens Duck.ai with `mode=voice-mode` (forcing a new session) instead of launching system voice search. > > Introduces `DuckChat.openVoiceDuckChat()` (implemented in `RealDuckChat` via `mode=voice-mode` query param), updates voice button visibility logic in `InputScreenViewModel` to be tab-aware, and wires both voice click handlers in `InputScreenFragment` to the new behavior. Adds new pixels (`m_aichat_voice_entry_tapped_*`) plus a shared `fireCountAndDaily` helper, and expands unit test coverage for the new routing/visibility/session semantics. > > <sup>Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit 0ed7e9a. This will update automatically on new commits. Configure [here](https://cursor.com/dashboard?tab=bugbot).</sup>
Task/Issue URL: https://app.asana.com/1/137249556945/project/1208671518894266/task/1213642299611483?focus=true ### Description Instead of skipping the workflow at the trigger level, the workflow now always runs but gates all jobs behind a check_changes job that inspects the diff. - If only `.md` or `.github/` files changed → all jobs are skipped (GitHub treats skipped as passing for branch protection) - If `ci.yml` itself changed → all jobs run normally, so regressions are caught before merging - If any code file changed → all jobs run normally ### Steps to test this PR QA optional: - Open a PR that only modifies an `.md` file → check_changes should pass, all other jobs should be skipped, PR should be mergeable - Open a PR that only modifies a `.github` workflow file (not `ci.yml`) → check_changes should pass, all other jobs should be skipped, PR should be mergeable - Open a PR that modifies `.github/workflows/ci.yml` → all jobs should run - Open a PR that modifies any source file → all jobs should run I tested all of these via #7962. <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Medium Risk** > Modifies the CI workflow execution logic to conditionally skip most jobs based on diff contents, which could unintentionally reduce coverage if the change-detection logic is wrong. > > **Overview** > The CI workflow no longer uses trigger-level `paths-ignore`; instead it always starts and runs a new `check_changes` job that diffs the PR (or always allows `push`/`workflow_dispatch`) to decide whether checks should run. > > All existing jobs (`code_formatting`, `unit_tests`, `lint`, `android_tests`) now `need` `check_changes` and are gated by `if: needs.check_changes.outputs.should_run == 'true'`, so docs-only or non-`ci.yml` `.github/` changes skip the expensive checks while changes to `ci.yml` or any code still run the full suite. > > <sup>Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit 9ff5ad8. This will update automatically on new commits. Configure [here](https://cursor.com/dashboard?tab=bugbot).</sup> <!-- /CURSOR_SUMMARY -->
#7955) Task/Issue URL: https://app.asana.com/1/137249556945/project/488551667048375/task/1213635828556477?focus=true ### Description Pins the version of the gradler-profiler to v0.23.0 to stabilize our metrics. Refs gradle/gradle-profiler#739. ### Steps to test this PR No QA needed. <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Low Risk** > Low risk CI workflow change that only affects the nightly benchmark job by making the `gradleprofiler` install deterministic. > > **Overview** > Pins the GitHub Actions nightly build benchmark workflow to install `gradleprofiler` version `0.23.0` via SDKMAN instead of the latest version, to stabilize benchmark results. > > <sup>Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit df240fc. This will update automatically on new commits. Configure [here](https://cursor.com/dashboard?tab=bugbot).</sup> <!-- /CURSOR_SUMMARY -->
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
See Commits and Changes for more details.
Created by
pull[bot]
Can you help keep this open source service alive? 💖 Please sponsor : )