Skip to content

Commit 172be5e

Browse files
malmsteinclaude
andcommitted
Force Duck.ai state in the contextual input widget
The contextual widget shares the per-tab input-state store with the main omnibar widget. For search-only users the omnibar publishes a browser/search state that leaked into the contextual sheet, flipping the tab off "chat" (hiding the tools row) and surfacing the search-only start-chat shortcut. applyState now forces DUCK_AI_CONTEXTUAL / DUCK_AI for the contextual widget so it always renders as a Duck.ai composer, and the StartChat plugin (a search-only address-bar shortcut that reads the shared state) is no longer added in the contextual widget. Also drops the obsolete setModelPickerEnabled assertion from the manager test, since the picker is now driven through the bound modelPickerEnabled flow after the develop merge. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent c3cfa14 commit 172be5e

2 files changed

Lines changed: 20 additions & 3 deletions

File tree

duckchat/duckchat-impl/src/main/java/com/duckduckgo/duckchat/impl/ui/nativeinput/views/NativeInputModeWidget.kt

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -365,6 +365,10 @@ class NativeInputModeWidget @JvmOverloads constructor(
365365
launch {
366366
viewModel.plugins.collect { plugins ->
367367
for (plugin in plugins) {
368+
// The start-chat shortcut is a search-only address-bar affordance; it has no place
369+
// in the contextual sheet's Duck.ai composer (and reads the shared per-tab state,
370+
// which can be search-only), so skip it there.
371+
if (isContextualWidget && plugin.containerId == R.id.startChatContainer) continue
368372
val container = findViewById<FrameLayout?>(plugin.containerId) ?: continue
369373
val pluginView = plugin.createView(context, this@NativeInputModeWidget)
370374
container.removeAllViews()
@@ -690,7 +694,19 @@ class NativeInputModeWidget @JvmOverloads constructor(
690694
floatingButtons?.setNewLineButtonVisible(visible)
691695
}
692696

693-
private fun applyState(state: NativeInputState) {
697+
private fun applyState(incomingState: NativeInputState) {
698+
// The contextual sheet is always a Duck.ai chat surface, but the shared per-tab state store can
699+
// carry a browser/search state written by the main omnibar widget (e.g. for search-only users).
700+
// Force the Duck.ai context/toggle here so the contextual widget never renders that search state,
701+
// which would otherwise flip the tab off "chat" (hiding the chat-tab controls).
702+
val state = if (isContextualWidget) {
703+
incomingState.copy(
704+
inputContext = NativeInputState.InputContext.DUCK_AI_CONTEXTUAL,
705+
toggleSelection = NativeInputState.ToggleSelection.DUCK_AI,
706+
)
707+
} else {
708+
incomingState
709+
}
694710
val previousState = nativeInputState
695711
val firstStateEmission = previousState == null
696712
val contextChanged = previousState?.inputContext != state.inputContext

duckchat/duckchat-impl/src/test/kotlin/com/duckduckgo/duckchat/impl/contextual/RealContextualNativeInputManagerTest.kt

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -158,7 +158,7 @@ class RealContextualNativeInputManagerTest {
158158
}
159159

160160
@Test
161-
fun `when native chat input enabled and onInputMode then card shown and picker enabled`() {
161+
fun `when native chat input enabled and onInputMode then card shown`() {
162162
val enabled = MutableStateFlow(true)
163163
whenever(duckChat.observeNativeChatInputEnabled()).thenReturn(enabled)
164164
val card = mockCard()
@@ -178,8 +178,9 @@ class RealContextualNativeInputManagerTest {
178178

179179
testee.onInputMode()
180180

181+
// The model picker is re-enabled via the bound modelPickerEnabled flow (covered separately);
182+
// here we assert the contextual card itself is shown in INPUT mode.
181183
verify(card).visibility = View.VISIBLE
182-
verify(widget).setModelPickerEnabled(true)
183184
}
184185

185186
@Test

0 commit comments

Comments
 (0)