Skip to content

[pull] develop from duckduckgo:develop#350

Open
pull[bot] wants to merge 5655 commits intoRachelmorrell:developfrom
duckduckgo:develop
Open

[pull] develop from duckduckgo:develop#350
pull[bot] wants to merge 5655 commits intoRachelmorrell:developfrom
duckduckgo:develop

Conversation

@pull
Copy link

@pull pull bot commented Aug 19, 2021

See Commits and Changes for more details.


Created by pull[bot]

Can you help keep this open source service alive? 💖 Please sponsor : )

malmstein and others added 22 commits February 6, 2026 17:21
…#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 |
| ------ | ----- |

|![before](https://github.com/user-attachments/assets/345cd1b6-4b19-4a61-9a4d-704335429632)|![after](https://github.com/user-attachments/assets/edf33a8c-c99e-4a2a-83c5-bbf4fee837fe)|

||![after](https://github.com/user-attachments/assets/f774569e-bf60-4e91-b140-2c37f2dd3e7e)|


<!-- 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 -->
daxmobile and others added 30 commits March 12, 2026 12:19
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 -->
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.