Skip to content

DuckAi/ChatTab: Add New search and remove New Tab option from + menu - #9761

Merged
karlenDimla merged 5 commits into
feature/karl/duckai-sidebar/history-context-menufrom
feature/karl/duckai-sidebar/plusmenuchanges
Sep 16, 2026
Merged

karlenDimla merged 5 commits into
feature/karl/duckai-sidebar/history-context-menufrom
feature/karl/duckai-sidebar/plusmenuchanges

Conversation

@karlenDimla

@karlenDimla karlenDimla commented Sep 9, 2026

Copy link
Copy Markdown
Contributor

Task/Issue URL: https://app.asana.com/1/137249556945/task/1218238186970934?focus=true
Tech Design URL (if applicable):
API Proposals URL(s) (if applicable):

Description

  • New "+" plus menu on the omnibar with New Chat, New Voice Chat, New Image (sidebar-only), New Search / New Tab, and New Fire Tab entries.
  • New Search (the sidebar relabel of "New Tab") opens a regular new tab and auto-surfaces the native input focused on the Search tab; with the flag off the entry stays "New Tab" and behaves as before.
  • Generalized the onboarding input-screen one-shot into InputScreenLaunchTarget (carries an InputMode instead of a DUCK_AI boolean) so both onboarding (Duck.ai tab) and New Search (Search tab) can drive the initial input mode.

Steps to test this PR

Setup

  • Internal build installed
  • Native input / input screen feature is active (native omnibar input widget with the Search / Duck.ai toggle is in use)
  • Do at least one duck.ai chat
  • Open the browser on a New Tab Page and focus the omnibar so the "+" plus button is available
  1. nativeDuckAiSidebar enabled (default) — open the "+" menu
  • Tap the "+" plus button in the omnibar
  • The menu entry (with the add-tab icon) reads New Search (not "New Tab")
  • The New Image entry IS visible
  1. nativeDuckAiSidebar enabled — tap New Search
  • A new tab opens (regular, non-Fire)
  • The native input widget IS auto-surfaced (keyboard shown), without a manual omnibar tap
  • The widget opens with the Search tab selected (not Duck.ai)
  • The input field is empty and ready to type
  • Typing a query and submitting performs a search (not a Duck.ai chat)
  1. nativeDuckAiSidebar disabled (via FF Inventory) — open the "+" menu
  • Tap the "+" plus button in the omnibar
  • The menu entry reads New Tab (not "New Search")
  • The New Image entry is NOT visible
  1. nativeDuckAiSidebar disabled — tap New Tab
  • A new tab opens (regular, non-Fire)
  • The tab lands with the address bar unfocused (native input is NOT force-opened)
  • No Search-tab pre-selection behavior occurs (unchanged pre-existing behavior)

Regression check

  • Duck.ai onboarding: completing the Search-path onboarding still auto-opens the input screen on the Duck.ai (chat) tab
  • Custom-AI onboarding still finishes on the Duck.ai (chat) tab
  • Focusing the omnibar on an existing tab (no menu action) still opens the input with the default toggle position — SEARCH is not spuriously forced

Note

Medium Risk
Touches new-tab creation, activity recreate/deferred actions, and native input initial mode; behavior is gated on nativeDuckAiSidebar but onboarding Duck.ai landing must stay correct.

Overview
When native Duck.ai sidebar is on, the omnibar + menu relabels New Tab to New Search, shows a New Image row (action still TODO), and opening that entry starts a regular tab with the native input screen on the Search tab—not Duck.ai.

Per-tab initial input mode is threaded through launchNewTabPendingAction.OpenNewTab (including bundle round-trip on browser-mode recreate) → pendingInputModeTargets / BrowserTabFragment.inputModeTargetBrowserTabViewModel.consumeInitialInputMode(), which prefers that one-shot target and still falls back to post-onboarding Duck.ai via OnboardingInputScreenLaunchTarget in the ViewModel (removed from the fragment).

Tests cover InputMode persistence across pending mode switch and one-shot consumption behavior.

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

karlenDimla commented Sep 9, 2026

Copy link
Copy Markdown
Contributor Author

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

Stale Bugbot comment from a previous run.

Comment thread app/src/main/java/com/duckduckgo/app/browser/BrowserTabViewModel.kt Outdated
@karlenDimla
karlenDimla force-pushed the feature/karl/duckai-sidebar/history-context-menu branch from c0ea2e3 to 3165abd Compare September 10, 2026 11:02
@karlenDimla
karlenDimla force-pushed the feature/karl/duckai-sidebar/plusmenuchanges branch from 685b489 to e6f494d Compare September 10, 2026 11:02
@karlenDimla karlenDimla changed the title DuckAi/ChatTab: Add New Image, New search and remove New Tab option from + menu DuckAi/ChatTab: Add New search and remove New Tab option from + menu Sep 10, 2026
@karlenDimla
karlenDimla force-pushed the feature/karl/duckai-sidebar/plusmenuchanges branch from e6f494d to d78dabf Compare September 10, 2026 12:19

@SingleInstanceIn(AppScope::class)
@ContributesBinding(AppScope::class)
class InputScreenLaunchTargetImpl @Inject constructor() : InputScreenLaunchTarget {

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.

@LukasPaczos I am repurposing the OnboardingInputScreenLauncher. Do youu mind taking a look and testing the usecase of that class?

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.

FWIW I prefer this since it takes the logic out of Onboarding and into a more purposeful API

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

LGTM. Just a comment for the future API. nice work!

Comment thread app/src/main/java/com/duckduckgo/app/browser/InputScreenLaunchTarget.kt Outdated

@SingleInstanceIn(AppScope::class)
@ContributesBinding(AppScope::class)
class InputScreenLaunchTargetImpl @Inject constructor() : InputScreenLaunchTarget {

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.

FWIW I prefer this since it takes the logic out of Onboarding and into a more purposeful API

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

The original OnboardingInputScreenLaunchTarget was deliberately scoped as narrowly as possible, and deliberately named Onboarding* so it wouldn't invite other uses. I think it's a dangerous API: it sets a token in global scope that gets consumed by whichever tab shows an input widget next. That could be the current tab regaining focus, or a new tab that opens next - the caller has no control over it. In practice it relies on the side effect that every new tab opens with the native input focused. If that side effect ever changes, every flow built on this launch target will quietly break. We also have race conditions where the outgoing tab briefly initializes while a new one is being created (case in point), which would consume the token prematurely.

Onboarding required this because it never sends an explicit "open a tab" or "open the input widget" command - it relies purely on the side effect, and we got away with it because we control that flow tightly enough. It works, but it's fragile, and if we ever change how CtaViewModel interacts with the BrowserTabViewModel, I'd like to remove it.

The new tab click handler integrated here is different: it already calls browserActivity?.launchNewTab(browserMode = BrowserMode.REGULAR) explicitly. That's a direct signal to the activity that we're launching a tab in this specific context. If we pass that context along to the created tab fragment and its VM, we get a much more direct and robust mechanism than the global token.

Could we explore that path first? Concretely, I mean something like:

browserActivity?.launchNewTab(browserMode = BrowserMode.REGULAR, inputModeTarget = InputMode.SEARCH)

and thread that through to nativeInputManager.showNativeInput, rather than widening InputScreenLaunchTarget to more callers and risking silent bugs and race conditions.

Only if that turns out not to be feasible would I consider extending InputScreenLaunchTarget.

Comment thread app/src/main/java/com/duckduckgo/app/browser/BrowserTabViewModel.kt Outdated
karlenDimla and others added 3 commits September 15, 2026 13:44
Wire the sidebar "New Search" menu action to open a regular new tab and
auto-surface its native input on the Search tab.

Generalize the onboarding input-screen one-shot into InputScreenLaunchTarget,
carrying an InputMode instead of a DUCK_AI boolean, and have showOrHideKeyboard
force the input open when a mode is armed.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The launch target is a process-lifetime singleton consumed only in
showNativeInput. When the native input field is inactive that path never
runs, so an armed target stayed set for the whole process and made
showOrHideKeyboard force focus on every call. Consume it in
showOrHideKeyboard for the non-native path to keep the force-focus
one-shot.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@karlenDimla
karlenDimla force-pushed the feature/karl/duckai-sidebar/history-context-menu branch from 1f24033 to b58e76e Compare September 15, 2026 17:50
@karlenDimla
karlenDimla force-pushed the feature/karl/duckai-sidebar/plusmenuchanges branch from d78dabf to aa68128 Compare September 15, 2026 17:50
@karlenDimla

Copy link
Copy Markdown
Contributor Author

@LukasPaczos @malmstein I thought about it addressed all your comments. For now, I removed the InputScreenLaunchTarget as I agree that this will require a proper thinking. Because of this, I had to add another variable in the BrowserActivity -> pendingInputModeTargets. I also don't like this approach but it is the one or the other. Let me know what you think!

@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 aabfdda. Configure here.

) {
this.tabId = tabId
this.skipHome = skipHome
this.inputModeTarget = inputModeTarget

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.

Launch mode lost on recreation

Medium Severity

loadData always overwrites inputModeTarget from the fragment field, which is not in arguments and comes back null after recreation. A surviving ViewModel then loses the New Search mode if consumeInitialInputMode has not run yet, so the input can open on the default toggle instead of Search.


Please tell me if this was useful or not with a 👍 or 👎.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit aabfdda. Configure 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.

This is not medium and could only occur as an edge case. Deferring for now.

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

LGTM. thanks for reworking this, threading the target per tab through the pending map is clearer than the global one-shot, and consumeInitialInputMode makes the fallback ordering obvious.

Comment thread app/src/main/java/com/duckduckgo/app/browser/BrowserActivity.kt Outdated
Route the "New Search" launch context to the specific new tab via
launchNewTab(inputModeTarget=SEARCH) instead of arming a global one-shot
that any tab could consume. It rides PendingAction.OpenNewTab across a
mode-switch recreate and is parked per-tabId until the tab's fragment is
built (mirroring externalLaunchTabIds / messageFromPreviousTab), so the
target reaches exactly the tab it opened.

Re-narrow the global signal back to onboarding-only
(OnboardingInputScreenLaunchTarget / setOpenOnDuckAi), and drop the
force-focus override from showOrHideKeyboard so a launch target no longer
silently outranks a CTA's focus-drop request.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@karlenDimla
karlenDimla force-pushed the feature/karl/duckai-sidebar/plusmenuchanges branch from aabfdda to c24acfb Compare September 16, 2026 17:36
Task/Issue URL:
https://app.asana.com/1/137249556945/project/1212087397361015/task/1218350178023317?focus=true
Tech Design URL (if applicable): 
API Proposals URL(s) (if applicable):
https://app.asana.com/1/137249556945/project/1201462763415876/task/1218365860196482?focus=true

### Description
- New Image (DuckChat.openDuckChatImageGeneration) switches the global
model to an accessible image-capable model when needed, falling back to
a normal new chat if none support image generation.

### Steps to test this PR

_nativeDuckAiSidebar enabled — entry visibility_
- [x] Open the "+" chat menu — New Image entry IS visible (with the
image icon)
  - [x] The entry below the divider now reads New Search

_nativeDuckAiSidebar enabled, currently selected model already supports
image generation_
- [x] Start a duck.ai chat from the NTP and set the Duck.ai model to one
that supports image generation
  - [x] Tap + and select New Image
- [x] A new Duck.ai chat tab opens (new session, not the existing chat)
  - [x] The native input is auto-focused on open
  - [x] The Image generation tool is preselected on the input
- [x] The selected model is unchanged (no model switch occurs) - You
might need to submit to see the model

_nativeDuckAiSidebar enabled, selected model does NOT support image
generation but another accessible model does_
- [x] Start a duck.ai chat from the NTP and set the Duck.ai model to one
WITHOUT image-generation support
  - [x] Tap + and select New Image
- [x] A new Duck.ai chat tab opens (new session, not the existing chat)
- [x] The selected model is switched to an accessible image-capable
model - You might need to submit to see the model
  - [x] The Image generation tool is preselected on the input
  - [x] The native input is auto-focused on open

_nativeDuckAiSidebar disabled (via FF inventory)_
- [x] Open the "+" chat menu — New Image entry is NOT visible
- [x] The entry below the divider still reads New Tab (not "New Search")




<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> **Medium Risk**
> Changes global Duck.ai model selection and cross-layer launch
plumbing; behavior is gated on model capabilities with a documented
fallback to a normal new chat.
> 
> **Overview**
> Wires the Duck.ai **+** menu **New Image** action to a full open flow:
**`BrowserTabViewModel.openNewImageDuckChat`** calls
**`DuckChat.openDuckChatImageGeneration`**, which may switch the global
model to an accessible image-capable model, then opens a **new** Duck.ai
session via **`BrowserNav.openDuckChat(..., forceImageGeneration)`**.
> 
> A one-shot **`forceImageGeneration`** flag travels on the browser
intent, is stashed in **`BrowserActivity`**, and is consumed when the
new tab first shows native input so **`NativeInputModeWidgetViewModel`**
preselects the image-generation tool and **`NativeInputManager`**
focuses the field. If no accessible model supports image generation, the
tab still opens but without forcing the tool.
> 
> <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit
a0c3718. 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: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown
Contributor

⚠️ API Proposal reminder

This PR changes the public surface of one or more -api modules, but the API Proposals field of the PR description is empty. This is a non-blocking reminder — please double-check whether a proposal is needed.

Changed -api source files:

  • browser-api/src/main/java/com/duckduckgo/app/tabs/BrowserNav.kt
  • duckchat/duckchat-api/src/main/java/com/duckduckgo/duckchat/api/DuckChat.kt

To clear this reminder, edit the PR description and either:

  • add the approved API Proposal link(s) in the API Proposals field (one per line), or
  • write None there if this PR does not change the public API (e.g. KDoc, comments, tests).

See the API Proposals section in .claude/docs/contributions.md for details.

@karlenDimla
karlenDimla merged commit d404aed into feature/karl/duckai-sidebar/history-context-menu Sep 16, 2026
13 checks passed
@karlenDimla
karlenDimla deleted the feature/karl/duckai-sidebar/plusmenuchanges branch September 16, 2026 17:43
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