Open Duck.ai entries in a new tab from the unified input#8700
Merged
Conversation
Under fullscreen mode the unified input's Duck.ai entries used to convert the current browser tab. Three paths now spawn a new tab instead, source-linked to the previous tab so the back arrow we already shipped returns the user to where they came from: - Toggle submit with Duck.ai selected - Chat-history suggestion tap - "Ask Duck.ai" autocomplete row A single helper on BrowserTabViewModel — openDuckAiQuery for query paths and openDuckAiChatById for chat-history's pre-built URLs — funnels all three through one decision. The shared private navigateToDuckAi adds the NTP carve-out: on an empty tab the chat loads in place so we don't leave the user staring at two empty tabs. Outside fullscreen mode every path falls through to today's legacy Intent behaviour. Command.SubmitChat had only the autocomplete caller and gets removed along with its handler in BrowserTabFragment. Asana: https://app.asana.com/1/137249556945/task/1215159003631554 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
16 tasks
joshliebe
reviewed
May 28, 2026
joshliebe
left a comment
Contributor
There was a problem hiding this comment.
I found a bug where the autocomplete isn’t dismissed when using “ask duck.ai”:
Screen_recording_20260528_145058.mp4
The new-tab Duck.ai paths added in this PR (toggle submit, chat-history suggestion, "Ask Duck.ai" autocomplete tap) all fire Command.OpenInNewTab while the autocomplete list or unified input is still on screen. Before this change, those overlays lived in the previous tab's fragment and remained visible on top of the newly opened tab. Mirror what Command.SwitchToTab already does: dismiss focused view, autocomplete list, and the native input before launching the new tab. The other callers of OpenInNewTab (webview long-press menus, etc.) never have these overlays shown, so the cleanup is a no-op for them. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
joshliebe
reviewed
May 28, 2026
joshliebe
approved these changes
May 28, 2026
joshliebe
left a comment
Contributor
There was a problem hiding this comment.
Works as expected now, LGTM
malmstein
added a commit
that referenced
this pull request
Jun 1, 2026
Task/Issue URL: https://app.asana.com/1/137249556945/project/1214157224317277/task/1215159003631560?focus=true ### Description Last piece of the iOS Ship Review nav model for Duck.ai. Two coordinated changes, both scoped to Duck.ai fullscreen context (no change anywhere else): 1. **Omnibar fire → "+" menu.** In `ViewMode.DuckAI` the omnibar's fire icon is replaced by a "+" that opens a popup menu. The swap is derived from the view mode at render time (no separate state flag that can drift), and `tabsMenu` follows whichever of fire/+ is visible via a barrier. 2. **Fire button moves into the chat input.** In a Duck.ai chat (bottom bar) the fire button sits at the leading edge of the input row, outside the card so the card's rounded corners don't clip it. The trailing in-widget fire is hidden there so the user only ever sees one fire affordance, and the leading fire hides while the input is focused. **The "+" popup menu** (`popup_chat_menu.xml`, shown anchored via the shared `PopupMenu.showAnchoredView` so it positions above/below depending on whether the omnibar is at the top or bottom): | Item | Action | |---|---| | New Chat | `openNewDuckChat` → `NativeAction.NEW_CHAT` | | New Voice Chat | `duckChat.openVoiceDuckChat()` | | New Tab | `launchNewTab` (fresh NTP) | | New Fire Tab | `launchNewTab` (same as New Tab for now) | **New Fire Tab** is only shown when `FireModeAvailability.isAvailable()` is true (feature flag + WebView MultiProfile support), re-checked each time the menu opens, so we don't surface it to users who can't use Fire Mode. Outside fullscreen mode (legacy Intent path), nothing changes — the omnibar never enters `ViewMode.DuckAI`. Stacks conceptually on #8699 (toggle removal + back arrow) and #8700 (new-tab navigation), but is independent and lands on its own. ### Steps to test this PR _Omnibar fire → +_ - [x] On a normal browser tab the omnibar shows the fire icon (unchanged). - [x] Open a Duck.ai chat. The omnibar shows **+** where fire was; the fire icon is gone; the tabs button stays correctly positioned next to it. - [x] Switch back to a browser tab — fire returns, + is gone. Confirm + never appears on NTP or in custom tabs. _+ popup menu_ - [x] Tap **+** in a Duck.ai chat. A popup opens anchored to the + (above it when the omnibar is at the bottom, below when at the top). - [x] **New Chat** starts a fresh chat. - [x] **New Voice Chat** opens voice mode. - [x] **New Tab** opens a fresh New Tab Page. - [ ] **New Fire Tab** opens a fresh New Tab Page (same as New Tab for now). _New Fire Tab gating_ - [ ] With Fire Mode available (flag on + supported WebView), **New Fire Tab** is present. - [x] With Fire Mode unavailable (flag off or unsupported WebView), **New Fire Tab** is hidden and the rest of the menu is intact. _Fire button in the chat input_ - [x] In a Duck.ai chat (bottom bar), a fire icon sits at the leading edge of the input row, fully visible (not clipped by the card corners). - [x] The trailing in-widget fire (next to tabs/menu) is not shown in chat. - [x] Tap the leading fire — the fire / clear-data dialog opens, same as the omnibar fire. - [x] Focus the input — the leading fire hides; defocus — it reappears. - [x] On a browser tab, the trailing fire is shown and the leading fire is hidden (unchanged). _Legacy mode (fullscreen mode off)_ - [ ] Disable the Duck.ai fullscreen-mode flag. No UI changes anywhere — Duck.ai opens via the legacy Intent path and the omnibar never enters DuckAI view mode. ### UI changes | Before | After | | ------ | ----- | !(Upload before screenshot)|(Upload after screenshot)| --------- Co-authored-by: David Gonzalez <malmstein@Davids-MacBook-Pro.local> Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
16 tasks
malmstein
added a commit
that referenced
this pull request
Jun 1, 2026
Task/Issue URL: https://app.asana.com/1/137249556945/project/1214157224317277/task/1215159003631560?focus=true ### Description Last piece of the iOS Ship Review nav model for Duck.ai. Two coordinated changes, both scoped to Duck.ai fullscreen context (no change anywhere else): 1. **Omnibar fire → "+" menu.** In `ViewMode.DuckAI` the omnibar's fire icon is replaced by a "+" that opens a popup menu. The swap is derived from the view mode at render time (no separate state flag that can drift), and `tabsMenu` follows whichever of fire/+ is visible via a barrier. 2. **Fire button moves into the chat input.** In a Duck.ai chat (bottom bar) the fire button sits at the leading edge of the input row, outside the card so the card's rounded corners don't clip it. The trailing in-widget fire is hidden there so the user only ever sees one fire affordance, and the leading fire hides while the input is focused. **The "+" popup menu** (`popup_chat_menu.xml`, shown anchored via the shared `PopupMenu.showAnchoredView` so it positions above/below depending on whether the omnibar is at the top or bottom): | Item | Action | |---|---| | New Chat | `openNewDuckChat` → `NativeAction.NEW_CHAT` | | New Voice Chat | `duckChat.openVoiceDuckChat()` | | New Tab | `launchNewTab` (fresh NTP) | | New Fire Tab | `launchNewTab` (same as New Tab for now) | **New Fire Tab** is only shown when `FireModeAvailability.isAvailable()` is true (feature flag + WebView MultiProfile support), re-checked each time the menu opens, so we don't surface it to users who can't use Fire Mode. Outside fullscreen mode (legacy Intent path), nothing changes — the omnibar never enters `ViewMode.DuckAI`. Stacks conceptually on #8699 (toggle removal + back arrow) and #8700 (new-tab navigation), but is independent and lands on its own. ### Steps to test this PR _Omnibar fire → +_ - [x] On a normal browser tab the omnibar shows the fire icon (unchanged). - [x] Open a Duck.ai chat. The omnibar shows **+** where fire was; the fire icon is gone; the tabs button stays correctly positioned next to it. - [x] Switch back to a browser tab — fire returns, + is gone. Confirm + never appears on NTP or in custom tabs. _+ popup menu_ - [x] Tap **+** in a Duck.ai chat. A popup opens anchored to the + (above it when the omnibar is at the bottom, below when at the top). - [x] **New Chat** starts a fresh chat. - [x] **New Voice Chat** opens voice mode. - [x] **New Tab** opens a fresh New Tab Page. - [ ] **New Fire Tab** opens a fresh New Tab Page (same as New Tab for now). _New Fire Tab gating_ - [ ] With Fire Mode available (flag on + supported WebView), **New Fire Tab** is present. - [x] With Fire Mode unavailable (flag off or unsupported WebView), **New Fire Tab** is hidden and the rest of the menu is intact. _Fire button in the chat input_ - [x] In a Duck.ai chat (bottom bar), a fire icon sits at the leading edge of the input row, fully visible (not clipped by the card corners). - [x] The trailing in-widget fire (next to tabs/menu) is not shown in chat. - [x] Tap the leading fire — the fire / clear-data dialog opens, same as the omnibar fire. - [x] Focus the input — the leading fire hides; defocus — it reappears. - [x] On a browser tab, the trailing fire is shown and the leading fire is hidden (unchanged). _Legacy mode (fullscreen mode off)_ - [ ] Disable the Duck.ai fullscreen-mode flag. No UI changes anywhere — Duck.ai opens via the legacy Intent path and the omnibar never enters DuckAI view mode. ### UI changes | Before | After | | ------ | ----- | !(Upload before screenshot)|(Upload after screenshot)| <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Low Risk** > UI and navigation changes in Duck.ai fullscreen only; Fire Mode menu item is gated; no auth or data-layer changes. > > **Overview** > For **fullscreen Duck.ai** (`ViewMode.DuckAI`), the omnibar **swaps the fire icon for a "+"** that opens a anchored popup (`popup_chat_menu.xml`) with New Chat, New Voice Chat, New Tab, and optionally **New Fire Tab** (gated by `FireModeAvailability` on each open). **New Fire Tab** currently calls the same `launchNewTab()` as New Tab. > > In the **Duck.ai bottom input bar**, a **leading fire** sits beside the input card; the in-widget trailing fire is hidden in `DUCK_AI` context, and the leading fire hides while the field is focused. Fire/clear-data is wired through `NativeInputCallbacks` / `onFireButtonTapped` from both placements. > > Omnibar layout uses a **barrier** so tabs align with whichever leading control is visible; transitions key off `isDuckAiMode` so fire/+ cannot drift from view mode. > > <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit 799555e. 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>
malmstein
added a commit
that referenced
this pull request
Jun 1, 2026
Task/Issue URL: https://app.asana.com/1/137249556945/project/1214157224317277/task/1215159003631560?focus=true ### Description Last piece of the iOS Ship Review nav model for Duck.ai. Two coordinated changes, both scoped to Duck.ai fullscreen context (no change anywhere else): 1. **Omnibar fire → "+" menu.** In `ViewMode.DuckAI` the omnibar's fire icon is replaced by a "+" that opens a popup menu. The swap is derived from the view mode at render time (no separate state flag that can drift), and `tabsMenu` follows whichever of fire/+ is visible via a barrier. 2. **Fire button moves into the chat input.** In a Duck.ai chat (bottom bar) the fire button sits at the leading edge of the input row, outside the card so the card's rounded corners don't clip it. The trailing in-widget fire is hidden there so the user only ever sees one fire affordance, and the leading fire hides while the input is focused. **The "+" popup menu** (`popup_chat_menu.xml`, shown anchored via the shared `PopupMenu.showAnchoredView` so it positions above/below depending on whether the omnibar is at the top or bottom): | Item | Action | |---|---| | New Chat | `openNewDuckChat` → `NativeAction.NEW_CHAT` | | New Voice Chat | `duckChat.openVoiceDuckChat()` | | New Tab | `launchNewTab` (fresh NTP) | | New Fire Tab | `launchNewTab` (same as New Tab for now) | **New Fire Tab** is only shown when `FireModeAvailability.isAvailable()` is true (feature flag + WebView MultiProfile support), re-checked each time the menu opens, so we don't surface it to users who can't use Fire Mode. Outside fullscreen mode (legacy Intent path), nothing changes — the omnibar never enters `ViewMode.DuckAI`. Stacks conceptually on #8699 (toggle removal + back arrow) and #8700 (new-tab navigation), but is independent and lands on its own. ### Steps to test this PR _Omnibar fire → +_ - [x] On a normal browser tab the omnibar shows the fire icon (unchanged). - [x] Open a Duck.ai chat. The omnibar shows **+** where fire was; the fire icon is gone; the tabs button stays correctly positioned next to it. - [x] Switch back to a browser tab — fire returns, + is gone. Confirm + never appears on NTP or in custom tabs. _+ popup menu_ - [x] Tap **+** in a Duck.ai chat. A popup opens anchored to the + (above it when the omnibar is at the bottom, below when at the top). - [x] **New Chat** starts a fresh chat. - [x] **New Voice Chat** opens voice mode. - [x] **New Tab** opens a fresh New Tab Page. - [ ] **New Fire Tab** opens a fresh New Tab Page (same as New Tab for now). _New Fire Tab gating_ - [ ] With Fire Mode available (flag on + supported WebView), **New Fire Tab** is present. - [x] With Fire Mode unavailable (flag off or unsupported WebView), **New Fire Tab** is hidden and the rest of the menu is intact. _Fire button in the chat input_ - [x] In a Duck.ai chat (bottom bar), a fire icon sits at the leading edge of the input row, fully visible (not clipped by the card corners). - [x] The trailing in-widget fire (next to tabs/menu) is not shown in chat. - [x] Tap the leading fire — the fire / clear-data dialog opens, same as the omnibar fire. - [x] Focus the input — the leading fire hides; defocus — it reappears. - [x] On a browser tab, the trailing fire is shown and the leading fire is hidden (unchanged). _Legacy mode (fullscreen mode off)_ - [ ] Disable the Duck.ai fullscreen-mode flag. No UI changes anywhere — Duck.ai opens via the legacy Intent path and the omnibar never enters DuckAI view mode. ### UI changes | Before | After | | ------ | ----- | !(Upload before screenshot)|(Upload after screenshot)| --------- Co-authored-by: David Gonzalez <malmstein@Davids-MacBook-Pro.local> Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.

Task/Issue URL: https://app.asana.com/1/137249556945/task/1215159003631554?focus=true
Description
Under fullscreen mode the unified input's Duck.ai entries used to convert
the current browser tab into a chat. Three paths now spawn a new tab
instead, source-linked to the previous tab so the back arrow (already
shipped in #8699) returns the user to where they came from:
A single helper on
BrowserTabViewModel—openDuckAiQueryfor querypaths and
openDuckAiChatByIdfor chat-history's pre-built URLs —funnels all three through one decision. The shared private
navigateToDuckAiadds an NTP carve-out: on an empty tab the chatloads in place so we don't leave the user staring at two empty tabs.
Outside fullscreen mode every path falls through to today's legacy
Intent behaviour, untouched.
Command.SubmitChatonly had the autocomplete caller and is removedalong with its handler in
BrowserTabFragment.Steps to test this PR
Toggle submit (Duck.ai selected)
Chat history suggestion
"Ask Duck.ai" autocomplete row
Existing Duck.ai chat unchanged
Legacy fallback (fullscreen mode off)
UI changes
Note
Medium Risk
Changes core tab/navigation and Duck.ai entry flows behind a feature flag, with broad unit test coverage; legacy paths are preserved when fullscreen is disabled.
Overview
When Duck.ai fullscreen mode is on, unified-input actions that used to replace the current tab now route through
openDuckAiQuery/openDuckAiChatByIdandnavigateToDuckAi: toggle submit, chat-history taps, and “Ask Duck.ai” autocomplete open a source-linked new tab on an active browser tab, and reuse the NTP tab when nothing is loaded yet. Legacy Intent behavior is unchanged when fullscreen is off.Command.SubmitChatis removed;NativeInputManagercallsonDuckAiQuerySubmittedinstead of pushing a URL via search submit.OpenInNewTabnow dismisses the focused overlay, autocomplete, and native input so they don’t sit on top of the new tab.Tests cover fullscreen vs legacy and NTP vs browser tab for each entry path.
Reviewed by Cursor Bugbot for commit 4a6bd24. Bugbot is set up for automated code reviews on this repo. Configure here.