Replace custom input with UTI in the contextual sheet#9091
Conversation
c54ad4b to
c3cfa14
Compare
| imagesJson = submitted.imagesJson, | ||
| filesJson = submitted.filesJson, | ||
| ) | ||
| }, |
There was a problem hiding this comment.
Quick action ignores UTI text
Medium Severity
With nativeChatInputEnabled, the quick-action handler still passes binding.inputField text into onQuickActionClicked, but the legacy EditText is hidden and the user types in contextualNativeInputWidget. Follow-up prefill and legacy summarize merging therefore see an empty string even when the UTI has content.
Reviewed by Cursor Bugbot for commit 172be5e. Configure here.
| binding.inputField.setSelection(viewState.prompt.length) | ||
| } else { | ||
| clearInputField() | ||
| } |
There was a problem hiding this comment.
Prompt state not synced to widget
Medium Severity
When nativeChatInputEnabled is true, renderViewState skips applying viewState.prompt to any visible composer. Updates such as replacePrompt from the summarize quick action only change ViewModel state, so the UTI stays empty and the user never sees the prefilled prompt.
Reviewed by Cursor Bugbot for commit 172be5e. Configure here.
| private val downloadMessagesJob = ConflatedJob() | ||
|
|
||
| private val binding: FragmentContextualDuckAiBinding by viewBinding() | ||
| private val binding: FragmentContextualDuckAiNativeBinding by viewBinding() |
There was a problem hiding this comment.
Keyboard resize ignores UTI focus
Medium Severity
The IME visibility listener only calls onKeyboardVisibilityChanged when the hidden legacy inputField has focus. Focusing the contextual NativeInputModeWidget opens the keyboard without expanding or half-expanding the sheet as before.
Reviewed by Cursor Bugbot for commit 172be5e. Configure here.
| if (viewState.nativeChatInputEnabled) { | ||
| // The unified input widget is the composer; ContextualNativeInputManager.onInputMode() | ||
| // shows its card. Hide the legacy EditText composer and its context chip/placeholder. | ||
| binding.contextualModeNativeContent.gone() |
There was a problem hiding this comment.
Manual page attach UI hidden
Medium Severity
With native chat input enabled, the entire legacy composer block—including duckAiAttachContextLayout and the page-context chip—is gone. Users who rely on manual “attach page content” (automatic attachment off and without the improved ask-about-page quick action) have no UI to attach context before sending from the UTI.
Reviewed by Cursor Bugbot for commit 172be5e. Configure here.
Make the unified input card in the contextual sheet look like the Duck.ai omnibar input card: it now floats with an all-around margin and fully rounded corners, sits on a daxColorDuckAiBackground surround, and uses a daxColorWindow fill so the rounded corners read against the background. Keep the bottom tools row (attachments, options, model, reasoning) always visible in the contextual sheet rather than only revealing it on focus, extracting the decision into a testable shouldShowBottomRow helper. Covered by NativeInputModeWidgetBottomRowTest. Note: on the initial (INPUT) sheet the tools row still measures zero height even though its visibility flags are correct; it renders fine in webview mode. That layout issue is tracked separately. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Set the contextual input container background per sheet mode: surface in the initial (INPUT) state and the Duck.ai page colour in webview mode, so the composer reads correctly against each backdrop. Revert the always-visible bottom row back to the focus-gated behaviour (matching the omnibar) and re-assert the plugin containers' visibility on focus in the contextual widget, so the tools row reveals with its controls when the input is focused. Known limitation: after the sheet is closed and reopened, the plugin controls can stay hidden until a full re-render (send a prompt / New chat). Tracked for follow-up. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
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>
172be5e to
80b3243
Compare
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
There are 5 total unresolved issues (including 4 from previous reviews).
❌ 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 80b3243. Configure here.
| } else { | ||
| // Chat already in progress: submit into the running chat via the JS event. | ||
| sendPrompt(prompt, modelId, reasoningEffort, selectedTool, imagesJson, filesJson) | ||
| } |
There was a problem hiding this comment.
Unset mode uses in-chat submit
Medium Severity
New chat routing requires lastMode == Mode.INPUT, but lastMode stays null until onInputMode or onWebViewMode runs. A submit before the first renderViewState takes the in-chat sendPrompt JS path instead of onNewChatPromptSubmitted, so the initial-sheet flow can miss starting a new chat.
Reviewed by Cursor Bugbot for commit 80b3243. Configure here.
| val selectedTool: String?, | ||
| val imagesJson: JSONArray?, | ||
| val filesJson: JSONArray?, | ||
| ) |
There was a problem hiding this comment.
Is this something we should extract in order to share between both managers (and then refactor)?
| onFilePickerRequested: (ValueCallback<Array<Uri>>, List<String>) -> Unit = { _, _ -> }, | ||
| // Invoked when the widget submits a prompt while the sheet is in INPUT mode: starts a new chat. | ||
| // WEBVIEW-mode submissions keep going through the in-chat JS event path (see setupWidget). | ||
| onNewChatPromptSubmitted: (NativeInputPrompt) -> Unit = {}, |
There was a problem hiding this comment.
Would onNewPromptSubmitted be a better name?
|
|
||
| renderPageContext(viewState.contextTitle, viewState.contextUrl, viewState.tabId) | ||
|
|
||
| if (viewState.quickActionState == DuckChatContextualViewModel.QuickActionState.ASK_ABOUT_PAGE) { |
There was a problem hiding this comment.
nit: I think a switch would be more readable here
joshliebe
left a comment
There was a problem hiding this comment.
Seems to work pretty well but there’s quite a lot of comments from cursor (and I left a few myself). Will recheck once those have been addressed.


Task/Issue URL: https://app.asana.com/1/137249556945/project/1174433894299346/task/1216113295942130?focus=true
Tech Design URL (if applicable):
Description
Integrates the unified text input (UTI)
NativeInputModeWidgetinto the Duck.ai contextual bottom sheet's initial (INPUT) state, gated on thenativeChatInputflag, so users get the full input options (model, reasoning, tools, attachments) when starting a contextual chat instead of the stripped-down legacy input.onPromptSent/generateContextPromptextended,ViewState.nativeChatInputEnabled, submit routed throughContextualNativeInputManager).daxColorWindowfill.daxColorSurfacein the initial sheet,daxColorDuckAiBackground(the Duck.ai page colour) in webview mode.Known limitation (why this is a draft): after the sheet is closed and reopened, the bottom-row controls can stay hidden until a full re-render (send a prompt / New chat / opening the input screen). Root cause is a stale layout/visibility state when the widget is reused across the sheet being hidden and shown again — tracked for follow-up.
Steps to test this PR
UTI in the contextual sheet
nativeChatInputenabled (default on internally)surfacein the initial sheet and the Duck.ai page colour in webview modenativeChatInputoff, the legacy input composer is unchangedUI changes
🤖 Generated with Claude Code
Note
Medium Risk
Changes how contextual chats start and what data is sent to the web layer, with shared per-tab native input state that could affect omnibar if mishandled; mitigated by feature flag and contextual-specific state overrides.
Overview
When
nativeChatInputis on, the Duck.ai contextual bottom sheet’s INPUT state usesNativeInputModeWidgetas the composer instead of the legacy EditText, while WEBVIEW mode still uses the widget for in-chat follow-ups via JS.Submit routing: INPUT-mode sends go through
NativeInputPromptandonNewChatPromptSubmitted→onPromptSent/generateContextPrompt, carrying model, reasoning, tool, and attachments from the widget (withDuckAiModelManagerfallback for quick actions). WEBVIEW-mode sends still usesubmitAIChatNativePrompt.UI: New
fragment_contextual_duck_ai_nativelayout (floating UTI card, quick-action prompts area, per-mode input container backgrounds).ContextualNativeInputManagershows the card in INPUT when the flag is enabled, uses fully rounded card corners, and hides the legacy composer in the fragment whennativeChatInputEnabledis true.Widget fixes for contextual reuse: Force
DUCK_AI_CONTEXTUALstate so omnibar search state doesn’t leak in, skip the start-chat plugin, and re-assert attach/options/model-picker visibility on focus.Reviewed by Cursor Bugbot for commit 80b3243. Bugbot is set up for automated code reviews on this repo. Configure here.