Skip to content

Add duck.ai entry point pixel - #9550

Open
YoussefKeyrouz wants to merge 1 commit into
developfrom
feature/youssef/duck_ai_entry_point_attribution
Open

Add duck.ai entry point pixel#9550
YoussefKeyrouz wants to merge 1 commit into
developfrom
feature/youssef/duck_ai_entry_point_attribution

Conversation

@YoussefKeyrouz

@YoussefKeyrouz YoussefKeyrouz commented Aug 20, 2026

Copy link
Copy Markdown
Collaborator

Task/Issue URL: https://app.asana.com/1/137249556945/project/1212810093780571/task/1217295011063283?focus=true
Tech Design URL (if applicable): https://app.asana.com/1/137249556945/project/481882893211075/task/1217382844057496?focus=true
API Proposals URL(s) (if applicable): https://app.asana.com/1/137249556945/project/1212810093780571/task/1217532302535418?focus=true

Description

Adds Duck.ai entry-point count/daily pixels with five bounded parameters: source, duck_ai_enabled, input_screen_enabled, opens_new_tab, and has_prompt.

Every public DuckChat.open* method requires a DuckChatEntryPoint and reports automatically. Navigation paths that bypass those methods call DuckChat.reportDuckChatEntry beside the navigation. Restored tabs and selecting an existing tab are passive and do not report.

Steps to test this PR

Install the internal build; filter Logcat for Pixel sent: and m_aichat_entry_point. Expect a count event for each action and at most one daily event per day. For every event, verify exactly source, duck_ai_enabled, input_screen_enabled, opens_new_tab, and has_prompt.

source Testing steps opens_new_tab has_prompt
address_bar_prompt NTP: Settings > AI Features > Search & Duck.ai; select Duck.ai in the address bar, enter entry test, submit.
Loaded page: repeat after opening https://example.com/.
NTP: false
Loaded page: true
true
address_bar_icon Settings > AI Features > Duck.ai Shortcuts > Address Bar on and Search Only selected. Open an NTP without focusing the bar; tap its Duck.ai icon. false false
suggestion_ask_ai Keep Search selected, focus the address bar, type entry test, and tap Ask Duck.ai. Repeat from an NTP and from https://example.com/. NTP: false
Loaded page: true
true
browsing_menu_ntp On an NTP, open ⋮ > New Chat. true false
browsing_menu_webpage Open https://example.com/, then ⋮ > New Chat. true false
tab_switcher Open the tab switcher and tap its Duck.ai toolbar/FAB button, not an existing Duck.ai tab card. true false
chat_history_new_chat With useNativeStorageChatData enabled, open ⋮ > Chats, then tap toolbar New; if history is empty, tap Open Duck.ai. true false
chat_history_open_chat Create a saved chat, open ⋮ > Chats, and tap its row. Also check an address-bar recent-chat suggestion: NTP reuses the tab; a loaded page opens a new tab. History/contextual row: true
Address-bar row: NTP false, loaded page true
false
voice Settings > AI Features > Duck.ai Shortcuts > Voice Chat on; open ⋮ > Voice Chat. true false
onboarding Clear app data/reinstall, choose the Duck.ai onboarding flow, enter/select a nonblank demo prompt, and continue into Duck.ai. true true
direct_url 1. Type and submit https://duck.ai/.
2. Type and submit https://duck.ai/chat?duckai=5&q=Hello%20from%20entry%20test&prompt=1.
3. Open https://duckduckgo.com/duckduckgo-help-pages/duckai/ and tap Navigate there directly.
Normal typed/intercepted navigation: false;
custom-tab handoff: true
Plain URL/link: false
Auto-submit URL: true
serp Open https://duckduckgo.com/?q=duck.ai, then tap the result that opens https://duck.ai/. false for normal same-tab navigation false
icon_shortcut Settings > AI Features > Duck.ai Shortcuts > Browser Menu on; relaunch if needed, long-press the DuckDuckGo app icon, then tap Duck.ai. true false
contextual_chat Enable contextualModeKillSwitch plus completed standalone migration or contextualMode; open contextual mode from a loaded page, then use the contextual FE handoff that emits openAIChat into full-screen Duck.ai. true false
widget_quick_actions Open Duck.ai once, add the Search and Duck.ai or Search and Favorites widget to the Home screen, then tap the Duck.ai icon in the widget. true false
widget_favorite Add https://duck.ai/ as a Favorite, add the Search and Favorites widget, then tap that Duck.ai favorite. true false
system_search Set Settings > AI Features > Search & Duck.ai; add a DuckDuckGo search widget, tap its search field (not its Duck.ai quick-action icon), enter entry test, then tap the Duck.ai icon inside System Search. true true
digital_assistant Android Settings > Apps > Default apps > Digital assistant app > DuckDuckGo; invoke the assistant gesture/power-key shortcut. true false
deep_link_other With the app closed, run adb shell am start -W -a android.intent.action.VIEW -d 'https://duck.ai/' -p '<applicationId>'. true false
paid_settings From the Settings main screen, under DuckDuckGo Subscription, tap Duck.ai, then Open Duck.ai. true false
address_bar_shortcut_chip NTP Customize > Shortcuts; enable Duck.ai, return to the NTP, tap the Duck.ai chip. false with the selected NTP;
true only in the no-selected-tab fallback
false
address_bar_editing_state Settings > AI Features > Search Only and Duck.ai Shortcuts > Address Bar on. Focus an empty address bar, then tap the Duck.ai icon shown in the editing field. true false

UI changes

No UI changes


Note

Medium Risk
Touches the public DuckChat open API and many navigation paths, so incorrect source/has_prompt tagging could skew analytics, but it does not change auth or chat behavior.

Overview
Adds m_aichat_entry_point count/daily pixels so every active Duck.ai entry reports a bounded source plus duck_ai_enabled, input_screen_enabled, opens_new_tab, and has_prompt.

Public DuckChat.open* / openVoiceDuckChat / openNewDuckChatSession now require a DuckChatEntryPoint and fire automatically. Paths that navigate without those methods (address bar, SERP/in-page links, widgets, onboarding, tab switcher, chat history, NTP chip, etc.) call reportDuckChatEntry beside the navigation. Restored/selected existing tabs stay unreported.

Also adds input_screen_enabled to the existing direct-navigation pixels.

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

@YoussefKeyrouz
YoussefKeyrouz force-pushed the feature/youssef/duck_ai_entry_point_attribution branch from 29001d4 to 0e965ef Compare August 20, 2026 07:02
@YoussefKeyrouz
YoussefKeyrouz force-pushed the feature/youssef/duck_ai_entry_point_attribution branch 2 times, most recently from e086b0f to 66dcdb8 Compare August 20, 2026 08:34
@YoussefKeyrouz
YoussefKeyrouz force-pushed the feature/youssef/wide_event_return_session branch from 7f575d2 to 7f795b8 Compare August 20, 2026 09:47
@YoussefKeyrouz
YoussefKeyrouz force-pushed the feature/youssef/duck_ai_entry_point_attribution branch 2 times, most recently from 77a8ba7 to ca18c92 Compare August 20, 2026 10:02
@YoussefKeyrouz
YoussefKeyrouz force-pushed the feature/youssef/wide_event_return_session branch from 7f795b8 to 9ba46aa Compare August 20, 2026 10:02

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

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 ca18c92. Configure here.

@YoussefKeyrouz
YoussefKeyrouz changed the base branch from feature/youssef/wide_event_return_session to graphite-base/9550 August 20, 2026 18:12
@YoussefKeyrouz
YoussefKeyrouz force-pushed the feature/youssef/duck_ai_entry_point_attribution branch from ca18c92 to 20a76d0 Compare August 20, 2026 19:18
@YoussefKeyrouz
YoussefKeyrouz changed the base branch from graphite-base/9550 to develop August 20, 2026 19:19

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

did not install or smoke-test the APK. nice work on the coverage, the truth-table test and the one that pins every enum to its wire value are the right tests to have written, and the contextual double-report case being tested explicitly is good to see. main things i'd like answered before merge are the has_prompt meaning on contextual expand and the narrowing of the existing direct-navigation pixel, since both change what analysts see. the rest is non-blocking: entry reporting would sit better in RealDuckChatPixels alongside fireCountAndDaily, and the prompt check now exists in three places with the url contract hardcoded in two of them.

"enum": ["true", "false"]
},
{
"key": "input_screen_enabled",

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.

fast-follow: duck_ai_enabled just above is still type string with a true/false enum, which the pixel rules say should be a boolean with no enum. since we're already editing these two entries, can we fix it in passing?

}
}

private fun hasAutoSubmittedPrompt(url: String): Boolean = runCatching {

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.

suggestion: this is the third copy of the prompt check (BrowserTabViewModel has the same function, BrowserWebViewClient inlines it), and this one hardcodes q and prompt while RealDuckChat owns those constants. shouldn't it live in duckchat next to PROMPT_QUERY_NAME so the three can't drift?

var urlToNavigate = queryUrlConverter.convertQueryToUrl(trimmedInput, verticalParameter, queryOrigin)

if (queryOrigin is QueryOrigin.FromUser && isTypedDuckAiUrl(urlToNavigate)) {
val isDuckAiDirectNavigation =

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.

q: requiring QuerySubmissionSource.USER narrows m_aichat_duck_ai_direct_navigation, so the series steps down from this release. intentional, and worth saying so in the pixel description?

DuckChatPixelParameters.OPENS_NEW_TAB to opensNewTab.toString(),
DuckChatPixelParameters.HAS_PROMPT to hasPrompt.toString(),
)
pixel.fire(DuckChatPixelName.DUCK_CHAT_ENTRY_POINT_COUNT, parameters = parameters)

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.

suggestion: every other duck chat pixel goes through DuckChatPixels, and fireCountAndDaily there already fires this pair on io. can we move entry reporting into RealDuckChatPixels instead of firing both from here on whatever thread tapped?

}
}

private fun DuckChatEntryPoint.toPixelValue(): String = when (this) {

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.

nit: RealDuckChatTest already asserts source equals entryPoint.name.lowercase() for every entry, so this when is 22 lines saying the same thing. can we just use name.lowercase()?

fun onFullModeRequested() {
logcat { "Duck.ai: request fullmode url $fullModeUrl" }
val currentState = _viewState.value
val hasPrompt = currentState.sheetMode != SheetMode.INPUT

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.

q: has_prompt is documented as whether a non-blank prompt is automatically submitted on entry, but expanding an existing chat to fullscreen doesn't submit anything, it restores the conversation. should this be false outside the INPUT case, or is the definition wording what needs changing?

}

override fun onClick(context: Context) {
duckChat.reportDuckChatEntry(DuckChatEntryPoint.ADDRESS_BAR_SHORTCUT_CHIP, opensNewTab = false, hasPrompt = 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.

q: we report opens_new_tab false here, but openInCurrentTab falls back to a new tab when there's no selected tab, which is the case the description's table calls out. can we derive it instead of hardcoding?

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