Skip to content

Enable native input for internal builds#8588

Merged
malmstein merged 1 commit into
developfrom
feature/david/enable_native_input_internal
May 29, 2026
Merged

Enable native input for internal builds#8588
malmstein merged 1 commit into
developfrom
feature/david/enable_native_input_internal

Conversation

@malmstein

@malmstein malmstein commented May 17, 2026

Copy link
Copy Markdown
Contributor

Task/Issue URL: https://app.asana.com/1/137249556945/project/1174433894299346/task/1214735022214304?focus=true

Description

Flips the nativeInputField feature flag default from FALSE to INTERNAL so internal builds get the native input widget automatically, and removes the now-redundant "Native Input Field" user toggle from AI Features settings. The widget is now shown purely based on the feature flag.

Implementation notes:

  • RealDuckChat caches duckChatFeature.nativeInputField().isEnabled() in a MutableStateFlow populated from cacheConfig(). The existing observeNativeInputFieldUserSettingEnabled() API stays on DuckChat and emits from that flow, so all consumers (omnibar layout, NTP, system search, native input manager, contextual native input manager, JS helper, cacheUserSettings) keep working without call-site changes.
  • The user-setting plumbing is gone: setter removed from DuckChatInternal/repository/data store, observe + read methods removed from repository/data store, preference key removed. DuckChatJSHelper's SUPPORTS_NATIVE_CHAT_INPUT / SUPPORTS_NATIVE_PROMPT now read the feature flag directly.

Steps to test this PR

Internal build

  • Install internal debug build (./gradlew :app:installInternalDebug)
  • Open AI Features settings — confirm there is no "Native Input Field" row
  • Tap the omnibar — confirm the native input widget appears (chat/search toggle, image attach, model picker)
  • Open a new tab — confirm the widget shows with the expected behaviour
  • Open Duck.ai contextual sheet on a browser tab — confirm native input is used

Play build (flag off by default)

  • Install play debug build (./gradlew :app:installPlayDebug)
  • Open AI Features settings — confirm there is no "Native Input Field" row
  • Tap the omnibar — confirm the native input widget does not appear and the existing web-based input is used
  • Flip aiChat / nativeInputField to enabled via the feature flags debug screen and relaunch — confirm the native input widget now appears

UI changes

Before After
(Upload before screenshot) (Upload after screenshot)

Note

Medium Risk
Changes how the native Duck.ai input is enabled by removing persisted user settings and switching gating to a remote feature flag, which could alter behavior for users/build variants if the flag state is misconfigured.

Overview
Enables Duck.ai native input for internal builds by default by changing nativeInputField’s default toggle value to INTERNAL and treating it as a capability/feature flag rather than a user preference.

Removes the “Native Input Field” setting from AI Features settings (UI, strings, ViewModel wiring) and deletes the associated repository/data-store plumbing and preference key; RealDuckChat now caches the flag in-memory and exposes it via the existing observeNativeInputFieldUserSettingEnabled() API to avoid call-site changes.

Updates DuckChatJSHelper to report SUPPORTS_NATIVE_CHAT_INPUT/SUPPORTS_NATIVE_PROMPT directly from the nativeInputField feature flag, and adjusts tests to reflect the new flag-driven behavior.

Reviewed by Cursor Bugbot for commit 2151b75. Bugbot is set up for automated code reviews on this repo. Configure here.

@malmstein malmstein marked this pull request as draft May 18, 2026 13:28
@malmstein malmstein force-pushed the feature/david/enable_native_input_internal branch 3 times, most recently from 75b4fc6 to 2e761aa Compare May 28, 2026 13:26
Flip the nativeInputField feature flag default to INTERNAL and
remove the now-redundant user toggle from AI Features settings.
Widget visibility is driven by the feature flag, with the existing
observer API in DuckChat backed by the cached flag value so all
consumers (omnibar, NTP, system search, native and contextual
input managers, JS helper) pick it up unchanged.

Adjust the Maestro flows that run on the internal binary so the
NTP omnibar — which now starts behind the native input widget —
is dismissable before the menu / address bar is needed:

- Add a shared ../shared/dismiss_native_input.yaml helper that
  taps the widget's back arrow (inputModeUnifiedBack) when visible.
- Have shared/browser_screen/click_on_menu_button.yaml dismiss the
  widget first, so any flow tapping the menu through the helper
  (custom tabs, ADS, sync) keeps working.
- Route privacy_tests_internal and duckplayer_settings through
  the same helper instead of tapping the menu directly.
- Dismiss the widget before tapping omnibarTextInput in the
  duckplayer direct/common flows and in sync's add_bookmarks step.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@malmstein malmstein force-pushed the feature/david/enable_native_input_internal branch from 2e761aa to 57c123d Compare May 28, 2026 13:59
@malmstein malmstein marked this pull request as ready for review May 28, 2026 19:08

@joshliebe joshliebe left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Works as expected! 🚀

@malmstein malmstein merged commit b1700bd into develop May 29, 2026
25 checks passed
@malmstein malmstein deleted the feature/david/enable_native_input_internal branch May 29, 2026 05:30
malmstein pushed a commit that referenced this pull request Jun 1, 2026
The Internal Privacy Tests job runs RequestBlocklistTest on the
internal build, where native input is now enabled by default
(#8588). With native input on, the omnibar disables the legacy
omnibarTextInput and routes taps through a click-catcher overlay,
so Espresso's typeText() failed with a PerformException on the
disabled, input-connection-less view.

Drive the unified input flow instead: after the warm-up page
loads, tap the omnibar click-catcher to open the input screen and
type the URL into the native inputField, mirroring the e2e tests.
Loading the warm-up page first also gives the privacy config time
to load before the request-blocklist page fires its requests,
which avoids a content-level race that a direct page load hit.

Verified on a Pixel 8 Pro emulator (API 34, same level as CI):
the test passes 4/4 runs.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
malmstein pushed a commit that referenced this pull request Jun 1, 2026
The Internal Privacy Tests job runs RequestBlocklistTest on the
internal build, where native input is now enabled by default
(#8588). With native input on, the omnibar disables the legacy
omnibarTextInput and routes taps through a click-catcher overlay,
so Espresso's typeText() failed with a PerformException on the
disabled, input-connection-less view.

Drive the unified input flow instead: after the warm-up page
loads, tap the omnibar click-catcher to open the input screen and
type the URL into the native inputField, mirroring the e2e tests.
Loading the warm-up page first also gives the privacy config time
to load before the request-blocklist page fires its requests,
which avoids a content-level race that a direct page load hit.

Verified on a Pixel 8 Pro emulator (API 34, same level as CI):
the test passes 4/4 runs.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
malmstein added a commit that referenced this pull request Jun 1, 2026
Task/Issue URL:
https://app.asana.com/0/1184843898389381/1215259352559550

### Description

The nightly **Internal Privacy Tests** job started failing on
`RequestBlocklistTest#whenRequestBlocklistIsEnabledRequestsAreHandledCorrectly`.
The failure is **not a privacy regression** — it's an Espresso
`PerformException`.

`Enable native input for internal builds (#8588)` set
`DuckChatFeature.nativeInputField()` to default `INTERNAL`. On internal
builds the omnibar now disables the legacy `omnibarTextInput` EditText
and overlays a click-catcher that routes taps to the unified input
screen (`OmnibarLayout.kt:1504-1533`, driven by
`showTextInputClickCatcher`). The test typed its URL straight into that
EditText, and Espresso refuses `typeText()` on a disabled view
(`is-enabled=false`, no input connection). The sibling `@PrivacyTest`
suite runs on the **play** build (native input off) and still passes —
the differential that confirms the cause.

Rather than bypassing the UI, this drives the **real internal-build
flow**: after the warm-up page loads, tap the omnibar click-catcher to
open the unified input screen and type the URL into the native
`inputField` (the same path the unified-input e2e tests exercise).
Loading the warm-up page first also lets the privacy config finish
loading before the request-blocklist page fires its requests, which
avoids a content-level race that a direct page load could hit.

### Steps to test this PR

_Internal Privacy Tests (API 34)_
- [x] Ran on a Pixel 8 Pro emulator (API 34, same level as CI's
`MediumPhone.arm-34`):
`RequestBlocklistTest#whenRequestBlocklistIsEnabledRequestsAreHandledCorrectly`
**passes 4/4 runs** (~75s each — opens the unified input, navigates, and
all assertions pass)
- [ ] The `Internal Privacy Tests` CI job passes — it runs this
`@InternalPrivacyTest` on `MediumPhone.arm-34`

> Note: cannot be verified on API 36 (Android 16) devices/emulators —
Espresso's input injection is incompatible there
(`InputManager.getInstance` `NoSuchMethodException`), unrelated to this
change. Run on API ≤34.

### UI changes

No UI changes — test-only change.

<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> **Low Risk**
> Test-only Espresso change with no production code, auth, or
data-handling impact.
> 
> **Overview**
> Updates **`RequestBlocklistTest`** so internal-build **Internal
Privacy Tests** match the native-input omnibar flow instead of typing
into the disabled legacy **`omnibarTextInput`**.
> 
> After the warm-up page loads, the test taps
**`omnibarTextInputClickCatcher`**, waits for the unified input screen,
then enters the request-blocklist URL in **`inputField`** (resolved at
runtime via **`getIdentifier`** to avoid importing **`:duckchat-impl`**
R IDs). WebView assertions are unchanged; only navigation setup differs.
> 
> <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit
60439bd. Bugbot is set up for automated
code reviews on this repo. Configure
[here](https://www.cursor.com/dashboard/bugbot).</sup>
<!-- /CURSOR_SUMMARY -->

Co-authored-by: David Gonzalez <malmstein@Davids-MacBook-Pro.local>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
malmstein added a commit that referenced this pull request Jun 1, 2026
Task/Issue URL:
https://app.asana.com/0/1184843898389381/1215259352559550

### Description

The nightly **Internal Privacy Tests** job started failing on
`RequestBlocklistTest#whenRequestBlocklistIsEnabledRequestsAreHandledCorrectly`.
The failure is **not a privacy regression** — it's an Espresso
`PerformException`.

`Enable native input for internal builds (#8588)` set
`DuckChatFeature.nativeInputField()` to default `INTERNAL`. On internal
builds the omnibar now disables the legacy `omnibarTextInput` EditText
and overlays a click-catcher that routes taps to the unified input
screen (`OmnibarLayout.kt:1504-1533`, driven by
`showTextInputClickCatcher`). The test typed its URL straight into that
EditText, and Espresso refuses `typeText()` on a disabled view
(`is-enabled=false`, no input connection). The sibling `@PrivacyTest`
suite runs on the **play** build (native input off) and still passes —
the differential that confirms the cause.

Rather than bypassing the UI, this drives the **real internal-build
flow**: after the warm-up page loads, tap the omnibar click-catcher to
open the unified input screen and type the URL into the native
`inputField` (the same path the unified-input e2e tests exercise).
Loading the warm-up page first also lets the privacy config finish
loading before the request-blocklist page fires its requests, which
avoids a content-level race that a direct page load could hit.

### Steps to test this PR

_Internal Privacy Tests (API 34)_
- [x] Ran on a Pixel 8 Pro emulator (API 34, same level as CI's
`MediumPhone.arm-34`):
`RequestBlocklistTest#whenRequestBlocklistIsEnabledRequestsAreHandledCorrectly`
**passes 4/4 runs** (~75s each — opens the unified input, navigates, and
all assertions pass)
- [ ] The `Internal Privacy Tests` CI job passes — it runs this
`@InternalPrivacyTest` on `MediumPhone.arm-34`

> Note: cannot be verified on API 36 (Android 16) devices/emulators —
Espresso's input injection is incompatible there
(`InputManager.getInstance` `NoSuchMethodException`), unrelated to this
change. Run on API ≤34.

### UI changes

No UI changes — test-only change.

<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> **Low Risk**
> Test-only Espresso change with no production code, auth, or
data-handling impact.
> 
> **Overview**
> Updates **`RequestBlocklistTest`** so internal-build **Internal
Privacy Tests** match the native-input omnibar flow instead of typing
into the disabled legacy **`omnibarTextInput`**.
> 
> After the warm-up page loads, the test taps
**`omnibarTextInputClickCatcher`**, waits for the unified input screen,
then enters the request-blocklist URL in **`inputField`** (resolved at
runtime via **`getIdentifier`** to avoid importing **`:duckchat-impl`**
R IDs). WebView assertions are unchanged; only navigation setup differs.
> 
> <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit
60439bd. Bugbot is set up for automated
code reviews on this repo. Configure
[here](https://www.cursor.com/dashboard/bugbot).</sup>
<!-- /CURSOR_SUMMARY -->

Co-authored-by: David Gonzalez <malmstein@Davids-MacBook-Pro.local>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants