Skip to content

Make Duck.ai onboarding compatible with native input field#8888

Open
lmac012 wants to merge 15 commits into
developfrom
feature/lukasz/duck-ai-onboarding-native-input-compatibility
Open

Make Duck.ai onboarding compatible with native input field#8888
lmac012 wants to merge 15 commits into
developfrom
feature/lukasz/duck-ai-onboarding-native-input-compatibility

Conversation

@lmac012

@lmac012 lmac012 commented Jun 15, 2026

Copy link
Copy Markdown
Contributor

Task/Issue URL: https://app.asana.com/1/137249556945/project/1205648422731273/task/1215660165459344?focus=true
Tech Design URL (if applicable):

Description

The Duck.ai onboarding flow (the in-context fire-button CTA and the end CTA) was built for the
classic omnibar/input-screen layout. When the duckChat.nativeInputField feature is enabled, the
fire button and chat controls move into the bottom native-input widget and the omnibar shows
different chrome, which broke the onboarding experience. This PR makes the flow work correctly in
that mode.

Changes:

  • End CTA as a home bubble. With nativeInputField on, the legacy InputScreenActivity path
    no longer fires, so the DAX_DUCK_AI_END CTA is rendered as a home-tab bubble on the NTP instead
    (legacy + brand-design variants), and dismisses correctly on the "High Five" tap.
  • Fire-button CTA highlight. The fire button now lives in the native-input widget, so the
    onboarding pulse is applied there instead of the (now-hidden) omnibar fire icon
    (setFireButtonSpotlightMode).
  • Lock everything but the fire button during the fire CTA. The native-input widget is dimmed and
    made non-interactive except for the fire button, and the omnibar controls that aren't covered by
    the existing onboarding lock are disabled too: the new-chat + icon, the Free plan pill, and
    the Duck.ai close (X) button.
  • Positioning. The end-CTA bubble is offset so the expanded native-input widget no longer covers it.

Legacy behaviour (with nativeInputField off) is unchanged.

Steps to test this PR

Apply this diff to make the duck.ai onboarding path available. The native input field is already
enabled in internal builds, so no other change is needed.

 override suspend fun enroll(): DuckAiOnboardingExperimentVariant? = withContext(dispatcherProvider.io()) {
     if (!arePrerequisitesMet()) return@withContext null

+    return@withContext DuckAiOnboardingExperimentVariant.TREATMENT_WITH_SEARCH_DEFAULT
+
     val toggle = extendedOnboardingFeatureToggles.onboardingDuckAiExperimentMay26()

 private fun arePrerequisitesMet(): Boolean =
     !appBuildConfig.isDefaultVariantForced &&
         browserConfig.singleTabFireDialog().isEnabled() &&
-        !onboardingBrandDesignUpdateToggles.brandDesignUpdate().isEnabled() &&
         !deviceInfo.isTablet()

Fire-button CTA

  • Run the Duck.ai onboarding flow and navigate to Duck.ai so the fire-button CTA appears
  • The fire button in the native-input widget pulses (not the omnibar)
  • The rest of the input widget is dimmed and unresponsive; the +, Free plan, and X buttons are dimmed and do nothing; only the fire button works
  • The input-bar background stays colour-uniform with the Duck.ai page (no darkened bar)
  • Tapping the fire button proceeds with the fire animation

End CTA

  • After the fire op, the end CTA appears as a bubble on the NTP (not a separate input screen)
  • The bubble is fully visible and not covered by the native-input widget
  • Toggle the input between Search and Duck.ai and confirm the bubble repositions to stay clear of the widget as it resizes
  • Tapping "High Five" dismisses the bubble and completes onboarding

Regression

  • Ensure the duckChat.nativeInputField flag is disabled, then do a clean install
  • Confirm the legacy onboarding / input-screen flow is unchanged

UI changes

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

Note

Medium Risk
Touches onboarding gating, back navigation, and native-input interaction during a guided flow; legacy paths are feature-flagged but the new bubble and lock wiring spans browser, CTA, and duckchat modules.

Overview
Makes Duck.ai onboarding work when the native input field is on instead of the legacy input screen.

End CTA on the NTP: If DuckAiFeatureState.showInputScreen is false, DAX_DUCK_AI_END is shown as home-tab bubbles (DaxDuckAiEndBubbleCta / brand-design variant) with the same impression and completion side effects as the input-screen path; custom-AI copy moves off generic DaxEndBrandDesignUpdateBubbleCta. Legacy input-screen mode still suppresses home CTAs until that flow runs.

Locked demo UX: Browser view state drives NativeInputState.InteractionLock and fire-button highlight on the widget (full lock vs lock except leading fire). Back is blocked while omnibar onboarding lock is active. Omnibar also disables +, Free plan, and Duck.ai back during lock; fire pulse targets visible fire chrome in Duck.ai/native-input mode.

Layout: isLogoOnly treats visible onboarding CTA bubbles as non-logo-only so NTP content offsets below the taller widget and bubbles stay visible.

PulseAnimation moves to browser-ui; pixel CTA params centralize in Pixel.PixelValues.

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

@lmac012 lmac012 changed the title Render Duck.AI end CTA as home bubble when nativeInputField is on Make Duck.ai onboarding compatible with native input field Jun 15, 2026

lmac012 commented Jun 15, 2026

Copy link
Copy Markdown
Contributor Author

@lmac012 lmac012 force-pushed the feature/lukasz/duck-ai-onboarding-native-input-compatibility branch 3 times, most recently from 3abd833 to dc7f3d4 Compare June 16, 2026 12:25
@lmac012 lmac012 requested a review from LukasPaczos June 16, 2026 12:46

@LukasPaczos LukasPaczos 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.

On a Pixel 9 Pro, in landscape, the CTA takes so much space that you can't see or scroll to reveal the response. Could we decrease the empty space at the bottom a little bit?
image

applyEnabled(clearTextButton, nonFireEnabled)
applyEnabled(duckAISidebar, nonFireEnabled)
applyEnabled(duckAIHeader, nonFireEnabled)
applyEnabled(duckAIFreePill, nonFireEnabled)

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.

Not a blocker but something I wanted to consider - any ideas how we could make this more robust so that it doesn't regress in the future? Instead of disabling each view independently (and people contributing new UI elements would need to remember to do so), could we apply a mask with semi-transparent foreground on the whole container and capture clicks so that they are not passed down to children? Or something similar?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Makes sense, I can look into it while working on the follow up task to hide duckAIFreePill entirely. As the requirements kept evolving, it wasn't clear we'd end up disabling everything, so I initially went with a simple yet flexible approach.


browserNavigationBarIntegration.configureFireButtonHighlight(highlighted = viewState.fireButton.isHighlighted())
browserNavigationBarIntegration.configureLockForOnboarding(locked = viewState.isOmnibarLockedForOnboarding)
nativeInputManager.setFireButtonSpotlightMode(viewState.fireButton.isHighlighted())

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.

We don't need to check viewState.isOmnibarLockedForOnboarding here?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

For the most part viewState.isOmnibarLockedForOnboarding is equal to viewState.fireButton.isHighlighted(). However, there is a tiny time window at the beginning before we start showing the fire button CTA + highlight, where we currently don't lock the native input field, but ideally we should, so I will look into improving this.

backgroundRes = CommonR.drawable.bg_onboarding_end,
shownPixel = AppPixelName.ONBOARDING_DAX_CTA_SHOWN,
okPixel = AppPixelName.ONBOARDING_DAX_CTA_OK_BUTTON,
ctaPixelParam = "duck_ai_end_cta",

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.

The original end CTA uses two different pixels for in-browser and NTP end CTAs, but here we use the same pixel for both. Should we use separate ones? Also, could you move it to a constant?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Unlike the original CTA, the duck ai end CTA is only shown in the context of NTP. I'm ignoring InputScreen, since that path is going to be disabled anyway.

I will extract this to a constant (it's not great that other CTAs keep these constants in the statistics-api module, but we can prioritize consistency here).

primaryCta = R.string.onboardingDuckAiEndCtaButton,
shownPixel = AppPixelName.ONBOARDING_DAX_CTA_SHOWN,
okPixel = AppPixelName.ONBOARDING_DAX_CTA_OK_BUTTON,
ctaPixelParam = "duck_ai_end_cta",

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.

Same as above.

@LukasPaczos

Copy link
Copy Markdown
Contributor

Using back button on the Duck.ai demo screen breaks the flow:

Screen_recording_20260616_155810.mp4

@lmac012

lmac012 commented Jun 17, 2026

Copy link
Copy Markdown
Contributor Author

Thanks for the review @LukasPaczos, I pushed a few commits to address (most of) your feedback. For the remaining items (CTA spacing in landscape orientation, disabling views in bulk) - I propose dealing with them as follow-ups.

The landscape issue seems to be shared with existing (reskinned) onboarding CTAs (but it is not as visible unless address bar is set to split). Ideally this should be solved adjusting the scrolling behavior (make CTAs scroll with the page content), but I'll have look if we can reduce the empty space below this particular CTA.

The back button is definitely a blocker, thanks for catching that. Fortunately, the fix seems to be simple.

@lmac012 lmac012 marked this pull request as ready for review June 17, 2026 12:43
@lmac012 lmac012 requested a review from LukasPaczos June 17, 2026 12:43
Comment thread app/src/main/java/com/duckduckgo/app/browser/BrowserTabFragment.kt
Comment thread app/src/main/java/com/duckduckgo/app/browser/BrowserTabFragment.kt
Comment thread app/src/main/java/com/duckduckgo/app/browser/BrowserTabViewModel.kt

@LukasPaczos LukasPaczos 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.

LGTM

if (!isAdded) return false
// During the locked Duck.ai onboarding demo, don't navigate within the app — returning false
// lets BrowserActivity exit (close the app). The user progresses by tapping the fire button.
if (viewModel.isOmnibarLockedForOnboarding()) return false

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.

This was flagged as a known issue for the initial experiment, but after the app closes and restarts here, we end up in an unlocked state, so user can interact with Duck.ai or even close it while the fire animation plays etc.

Longer term, I think this flow should move to the linear onboarding orchestrator, which forces the Duck.ai browser CTAs into a linear flow and handles aborting correctly (restarts from scratch).

Either way, since we already deemed this an edge case, it's not a blocker here.

@LukasPaczos

Copy link
Copy Markdown
Contributor

btw. I tested with forced native input, as it's rolling out now:

diff --git a/duckchat/duckchat-impl/src/main/java/com/duckduckgo/duckchat/impl/feature/DuckChatFeature.kt b/duckchat/duckchat-impl/src/main/java/com/duckduckgo/duckchat/impl/feature/DuckChatFeature.kt
index 151e8b0c21..6043dee436 100644
--- a/duckchat/duckchat-impl/src/main/java/com/duckduckgo/duckchat/impl/feature/DuckChatFeature.kt
+++ b/duckchat/duckchat-impl/src/main/java/com/duckduckgo/duckchat/impl/feature/DuckChatFeature.kt
@@ -142,6 +142,7 @@ interface DuckChatFeature {
      * @return `true` when the Native Input Field should be used instead of the web-based input.
      */
     @Toggle.DefaultValue(DefaultFeatureValue.INTERNAL)
+    @InternalAlwaysEnabled
     fun nativeInputField(): Toggle

@lmac012 lmac012 force-pushed the feature/lukasz/duck-ai-onboarding-native-input-compatibility branch from a305231 to 94f4eb5 Compare June 18, 2026 00:39
@lmac012 lmac012 force-pushed the feature/lukasz/duck-ai-end-dialog-brand-design-update branch from 9acd1af to bf0c9c4 Compare June 18, 2026 00:39
is DaxBubbleCta.DaxEndCta,
is DaxEndBrandDesignUpdateBubbleCta,
is DaxDuckAiEndBubbleCta,
is DaxDuckAiEndBrandDesignUpdateBubbleCta,

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.

Missing final dialog press metric

Low Severity

Tapping High five! on the native-input DaxDuckAiEnd* home bubble fires the standard OK pixel via onUserClickCtaOkButton, but never calls duckAiOnboardingExperimentMetrics.fireFinalDialogPressed(). The legacy input-screen path still reports that metric through onDuckAiEndCtaInputScreenResultonDuckAiEndCtaInteraction.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 94f4eb5. Configure here.

Base automatically changed from feature/lukasz/duck-ai-end-dialog-brand-design-update to develop June 18, 2026 01:04
@lmac012 lmac012 force-pushed the feature/lukasz/duck-ai-onboarding-native-input-compatibility branch from 94f4eb5 to dd85114 Compare June 18, 2026 01:08
dismissedCtaDao.insert(DismissedCta(CtaId.DAX_DUCK_AI_END))
completeStageIfDaxOnboardingCompleted()
if (canSendShownPixel(onboardingStore, DUCK_AI_END_CTA_PIXEL_PARAM)) {
val journey = addCtaToHistory(onboardingStore, appInstallStore, DUCK_AI_END_CTA_PIXEL_PARAM)

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.

Missing final dialog pressed metric

Medium Severity

Tapping High five on the native-input DaxDuckAiEndBubbleCta / brand-design end bubble fires the standard OK pixel via onUserClickCtaOkButton, but never calls duckAiOnboardingExperimentMetrics.fireFinalDialogPressed(). The legacy input-screen path still sends that metric through onDuckAiEndCtaInteraction, so Duck.ai onboarding experiment conversion data is incomplete for users on the new NTP bubble flow.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit dd85114. Configure here.

@malmstein malmstein 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.

This change not only affects Onboarding, because you are setting a “state” that is computed outside onboarding too.

I’d like this to either use the existing path to update the input field state or create a separation that only affects onboarding.

The suggestion to disabling views in bulk needs to go through O-J since we are about refactor much of the tech debt added.

* [EnabledState.DISABLED] none, [EnabledState.FIRE_BUTTON_SPOTLIGHT] only the fire button
* (highlighted with a pulse).
*/
enum class EnabledState { ENABLED, DISABLED, FIRE_BUTTON_SPOTLIGHT }

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.

why is this here? There’s a NativeInputState that represents the current state. Please use the existing pattern, there’s no need to duplicate it for the sake of onboarding.

}
}

override fun setEnabledState(state: EnabledState) {

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.

same as above, there’s a NativeInputState and a NativeInputStatePublisher that are in charge of setting the state of the input field. What happens here is exclusively related to onboarding and pulse animations, so I’d prefer that you either use the existing path to change the input field state or create a proper separation that only affects the Onboarding path.

Calling this enabledState is not correct.

LukasPaczos added a commit that referenced this pull request Jun 18, 2026
Fixes a regression in #8888.
The PR introduced a new end CTA for DUck.ai flow, so the custom AI copy
needs to move to it.

<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> **Low Risk**
> Onboarding copy routing only; no auth, data, or core browser behavior
changes.
> 
> **Overview**
> Fixes a regression where **custom AI onboarding** end messaging lived
on the generic **Dax end** bubble after a separate **Duck AI end** CTA
was added.
> 
> **`DaxDuckAiEndBrandDesignUpdateBubbleCta`** now picks the description
from `onboardingStore.isCustomAiOnboardingFlow()`: custom flow uses
`onboardingEndCustomAiFlowDaxDialogDescription`, otherwise the standard
Duck AI end string.
> 
> **`DaxEndBrandDesignUpdateBubbleCta`** is simplified back to always
using `onboardingEndDaxDialogDescription`, with the custom-flow branch
and `decorateDescription` AI icon logic removed from that class.
> 
> <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit
b8b0711. Bugbot is set up for automated
code reviews on this repo. Configure
[here](https://www.cursor.com/dashboard/bugbot).</sup>
<!-- /CURSOR_SUMMARY -->
lmac012 added 2 commits June 18, 2026 18:11
When DuckChatFeature.nativeInputField() is enabled the legacy InputScreenActivity
auto-launch never fires (RealDuckChat forces showInputScreen=false), so after the
in-context fire op the user lands on the NTP with the duck.ai onboarding stuck —
no DAX_DUCK_AI_END CTA, no follow-up Privacy Pro CTA, stage frozen at DAX_ONBOARDING.

Add two bubble-CTA variants (legacy DaxDuckAiEndBubbleCta + brand-design
DaxDuckAiEndBrandDesignUpdateBubbleCta) and surface them from CtaViewModel.getHomeCta
when the input screen is not enabled. The standard onCtaShown lifecycle marks the
CTA as shown (markAsReadOnShow=true) and now also fires the FinalDialogImpression
metric and completes the onboarding stage — mirroring what
prepareAndMarkDuckAiEndCtaForInputScreen does for the legacy path. The legacy
InputScreenActivity path is untouched (suppression preserved when isInputScreenEnabled).

Both variants apply the same ic_ai_chat_16 icon-suffix the InputScreenFragment
renderer uses today. The brand-design variant hooks decorateDescription; the legacy
variant wraps onTypingAnimationFinished to overwrite the typed text once the
animation completes (TypeAnimationTextView types plain strings only).
BrowserTabViewModel.onDaxBubbleCtaOkButtonClicked has an explicit when branch
for DaxBubbleCta.DaxEndCta / DaxEndBrandDesignUpdateBubbleCta that calls
refreshCta() and pipes the result back into ctaViewState — which is what
actually swaps the bubble view off the screen. The two new Duck.AI end bubble
CTAs fell into the else branch, so tapping High Five fired the OK pixel but
left the bubble visible. Add both to the existing branch.
lmac012 and others added 10 commits June 18, 2026 18:11
When nativeInputField is on, the fire button lives in the bottom widget,
not the omnibar, so the onboarding pulse no longer landed on it. Add
NativeInputManager.setFireButtonSpotlightMode to pulse the widget's
leading fire button, forward the highlight from BrowserTabFragment, and
stop the omnibar from pulsing its hidden fire icon in Duck.AI mode.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The omnibar is already locked to FIRE_BUTTON_ONLY during the CTA;
applyEnabledState just wasn't dimming/disabling the + icon that replaces
the fire button in Duck.AI native-input mode.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add NativeInputWidget.setInteractionLocked, which dims the (transparent)
widget and swallows touches so only the leading fire button — a sibling
outside the widget — stays usable. Dimming the widget rather than its
parent card keeps the input bar colour-uniform with the page. Driven from
setFireButtonSpotlightMode alongside the pulse.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The Duck.AI end CTA bubble lives inside the NTP (newTabContent), which
NativeInputLayoutCoordinator.configureContentOffset already offsets below
the native input widget — except it suppresses that offset for logo-only
NTPs to keep the logo centered. Skip the suppression while an onboarding
CTA bubble is visible so the bubble clears the widget in that case too.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The Duck.AI "Free plan" pill is shown and click-wired by
NativeInputOmnibarController, so the onboarding lock's applyEnabledState
never disabled it and it still opened the subscriptions screen. Disable
and dim it alongside the other non-fire omnibar buttons.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…arding

The "X" that closes Duck.AI (duckAiBack) wasn't in applyEnabledState, so
the onboarding lock left it tappable. Disable and dim it alongside the
other non-fire omnibar buttons.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Promote setFireButtonSpotlightMode to setEnabledState(EnabledState) so the
widget models all three onboarding states like the omnibar/nav bar: ENABLED,
DISABLED (locked before the CTA), and FIRE_BUTTON_SPOTLIGHT. Previously the
widget only locked once the fire CTA appeared, leaving it interactive during
the earlier isOmnibarLockedForOnboarding window.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
During the full-screen Duck.ai onboarding demo the omnibar/widget are locked
(isOmnibarLockedForOnboarding), but the system Back button wasn't, so pressing
it tore down the native widget via hideNativeInput() and navigated within the
app, leaving the user on a permanently-locked omnibar. While locked, return
false from onBackPressed (before hideNativeInput) so BrowserActivity's existing
unhandled-back path exits the root activity — closing the app — without tearing
the widget down. The user progresses by tapping the fire button.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The duck_ai_end_cta / duck_ai_fire_button_cta pixel-param strings were
hardcoded across the CTA classes and CtaViewModel. Move them into
Pixel.PixelValues (DUCK_AI_END_CTA, DUCK_AI_FIRE_BUTTON_CTA) for consistency
with the other CTA params, and reference the constants everywhere.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Fixes a regression in #8888.
The PR introduced a new end CTA for DUck.ai flow, so the custom AI copy
needs to move to it.

<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> **Low Risk**
> Onboarding copy routing only; no auth, data, or core browser behavior
changes.
> 
> **Overview**
> Fixes a regression where **custom AI onboarding** end messaging lived
on the generic **Dax end** bubble after a separate **Duck AI end** CTA
was added.
> 
> **`DaxDuckAiEndBrandDesignUpdateBubbleCta`** now picks the description
from `onboardingStore.isCustomAiOnboardingFlow()`: custom flow uses
`onboardingEndCustomAiFlowDaxDialogDescription`, otherwise the standard
Duck AI end string.
> 
> **`DaxEndBrandDesignUpdateBubbleCta`** is simplified back to always
using `onboardingEndDaxDialogDescription`, with the custom-flow branch
and `decorateDescription` AI icon logic removed from that class.
> 
> <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit
b8b0711. Bugbot is set up for automated
code reviews on this repo. Configure
[here](https://www.cursor.com/dashboard/bugbot).</sup>
<!-- /CURSOR_SUMMARY -->
@lmac012 lmac012 force-pushed the feature/lukasz/duck-ai-onboarding-native-input-compatibility branch from 16cf7c1 to ae3eb36 Compare June 18, 2026 16:29
lmac012 and others added 2 commits June 18, 2026 21:11
Relocate PulseAnimation (and its ic_circle_pulse_blue drawable) from :app to :browser-ui so it can be shared with duckchat-impl. Pure move with no behavior change; updates the three import sites (OmnibarLayout, BrowserNavigationBarView, NativeInputManager).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Replaces the imperative NativeInputManager.setEnabledState(EnabledState) API with two NativeInputState fields (interactionLocked, fireButtonHighlighted) that the widget renders, per review feedback to use the existing NativeInputState pattern. NativeInputManager now only publishes the onboarding signal via configureLockForOnboarding/configureFireButtonHighlight; the widget applies the body lock and the leading fire-button dim/pulse (PulseAnimation, now in :browser-ui). BrowserTabFragment forwards the two viewState booleans, mirroring the nav-bar integration.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
updateBackButtons(state)
updateFireButtonVisibility(state)
updateDuckAiFireButtonHighlight(state)
setInteractionLocked(state.interactionLocked)

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.

Fire CTA pulse after focus

Medium Severity

In applyState, updateFireButtonVisibility and updateDuckAiFireButtonHighlight run before setInteractionLocked. The leading fire icon is hidden while the input has focus, and setInteractionLocked(true) clears focus afterward without re-running those updates, so the fire control can stay hidden and the onboarding pulse never starts until another state emission.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 888423c. Configure here.

@lmac012 lmac012 requested a review from malmstein June 19, 2026 12:45

@malmstein malmstein 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.

This looks great @lmac012 . Thanks for taking the time to work through this, our future refactor will thank you!

Models the native input's interaction-lock state as Unlocked / Locked /
LockedExceptDuckAiFireButton instead of a boolean whose fire-button-exemption
was derived in the widget. The fire-button pulse stays on its own
duckAiFireButtonHighlighted flag.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@lmac012 lmac012 force-pushed the feature/lukasz/duck-ai-onboarding-native-input-compatibility branch from a49e483 to 5486113 Compare June 22, 2026 09:29

@cursor cursor Bot 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.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

There are 4 total unresolved issues (including 3 from previous reviews).

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, have a team admin enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 5486113. Configure here.

ctaPixelParam = Pixel.PixelValues.DUCK_AI_END_CTA,
onboardingStore = onboardingStore,
appInstallStore = appInstallStore,
) {

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.

Custom AI end copy missing

Low Severity

When brand-design update is off, DaxDuckAiEndBubbleCta always uses the generic end description. DaxDuckAiEndBrandDesignUpdateBubbleCta switches to onboardingEndCustomAiFlowDaxDialogDescription for custom AI onboarding, so those users on the legacy bubble see the wrong end-CTA copy.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 5486113. Configure here.

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.

3 participants