Skip to content

Open Duck.ai entries in a new tab from the unified input#8700

Merged
malmstein merged 2 commits into
developfrom
feature/david/duckai_opens_in_new_tab
May 28, 2026
Merged

Open Duck.ai entries in a new tab from the unified input#8700
malmstein merged 2 commits into
developfrom
feature/david/duckai_opens_in_new_tab

Conversation

@malmstein

@malmstein malmstein commented May 27, 2026

Copy link
Copy Markdown
Contributor

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:

  • Toggle submit with Duck.ai selected
  • Chat-history suggestion tap
  • "Ask Duck.ai" autocomplete row

A single helper on BrowserTabViewModelopenDuckAiQuery for query
paths and openDuckAiChatById for chat-history's pre-built URLs —
funnels all three through one decision. The shared private
navigateToDuckAi adds an 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, untouched.

Command.SubmitChat only had the autocomplete caller and is removed
along with its handler in BrowserTabFragment.

Steps to test this PR

Toggle submit (Duck.ai selected)

  • On a browser tab with content, focus the input and switch the toggle to Duck.ai.
  • Submit a query. A new tab opens with Duck.ai showing the chat; the original tab stays in place behind it.
  • Tap the back arrow next to the chat-history icon. The original tab is foregrounded.
  • Repeat the test from an NTP (open a new tab, do not load a URL). Submitting the query reuses the NTP tab — no extra empty tab is spawned.

Chat history suggestion

  • Have a couple of existing Duck.ai chats in history.
  • On a browser tab with content, focus the input and switch the toggle to Duck.ai. Tap one of the chat-history suggestions.
  • A new tab opens on the selected chat. Back arrow returns to the originating tab.
  • Repeat from NTP. The NTP tab is reused for the selected chat.

"Ask Duck.ai" autocomplete row

  • On a browser tab with content, focus the input with the Search tab selected and type a query.
  • Tap the "Ask Duck.ai" row in the autocomplete suggestions list.
  • A new tab opens with Duck.ai answering the prompt; the original tab stays.
  • Repeat from NTP. NTP is reused.

Existing Duck.ai chat unchanged

  • Open a Duck.ai chat, focus the input, send a follow-up message. The message reaches the existing chat — no new tab is spawned.

Legacy fallback (fullscreen mode off)

  • Disable the Duck.ai fullscreen mode feature flag.
  • Repeat the toggle-submit and autocomplete flows. Chat opens via the legacy Intent activity exactly as before; no behaviour change.

UI changes

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

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 / openDuckAiChatById and navigateToDuckAi: 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.SubmitChat is removed; NativeInputManager calls onDuckAiQuerySubmitted instead of pushing a URL via search submit. OpenInNewTab now 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.

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>

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

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

Still seeing the same behavior, you can repro by tapping "ask duck.ai" in autocomplete and going back to the tab:

Image

@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 now, LGTM

@malmstein
malmstein merged commit a825250 into develop May 28, 2026
40 checks passed
@malmstein
malmstein deleted the feature/david/duckai_opens_in_new_tab branch May 28, 2026 21:09
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>
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>
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