Skip to content

Replace custom input with UTI in the contextual sheet#9091

Open
malmstein wants to merge 5 commits into
developfrom
07-02-uti_add_to_contextual_sheet
Open

Replace custom input with UTI in the contextual sheet#9091
malmstein wants to merge 5 commits into
developfrom
07-02-uti_add_to_contextual_sheet

Conversation

@malmstein

@malmstein malmstein commented Jul 2, 2026

Copy link
Copy Markdown
Contributor

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) NativeInputModeWidget into the Duck.ai contextual bottom sheet's initial (INPUT) state, gated on the nativeChatInput flag, so users get the full input options (model, reasoning, tools, attachments) when starting a contextual chat instead of the stripped-down legacy input.

  • INPUT mode now shows the UTI widget as the composer; submitting starts a new chat carrying the widget's model / reasoning / tool / attachment selections (onPromptSent / generateContextPrompt extended, ViewState.nativeChatInputEnabled, submit routed through ContextualNativeInputManager).
  • Styled the input card to match the Duck.ai omnibar input: floating card with margins and fully rounded corners on a daxColorWindow fill.
  • The input container background is set per sheet mode — daxColorSurface in the initial sheet, daxColorDuckAiBackground (the Duck.ai page colour) in webview mode.
  • The bottom tools row is focus-gated (matching the omnibar) and re-asserted on focus in the contextual widget.

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

  • Internal build with nativeChatInput enabled (default on internally)
  • Open a web page, then open the Duck.ai contextual sheet
  • Initial sheet shows the unified input card (Duck.ai-styled); focusing the input reveals the tools row (attach, options, model)
  • Type a prompt and submit → a new chat starts in the sheet (webview mode) using the selected model/reasoning/tool
  • Input container background is surface in the initial sheet and the Duck.ai page colour in webview mode
  • With nativeChatInput off, the legacy input composer is unchanged

UI changes

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

🤖 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 nativeChatInput is on, the Duck.ai contextual bottom sheet’s INPUT state uses NativeInputModeWidget as 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 NativeInputPrompt and onNewChatPromptSubmittedonPromptSent / generateContextPrompt, carrying model, reasoning, tool, and attachments from the widget (with DuckAiModelManager fallback for quick actions). WEBVIEW-mode sends still use submitAIChatNativePrompt.

UI: New fragment_contextual_duck_ai_native layout (floating UTI card, quick-action prompts area, per-mode input container backgrounds). ContextualNativeInputManager shows the card in INPUT when the flag is enabled, uses fully rounded card corners, and hides the legacy composer in the fragment when nativeChatInputEnabled is true.

Widget fixes for contextual reuse: Force DUCK_AI_CONTEXTUAL state 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.

@malmstein malmstein force-pushed the 07-02-uti_add_to_contextual_sheet branch from c54ad4b to c3cfa14 Compare July 2, 2026 20:45
@malmstein malmstein marked this pull request as ready for review July 2, 2026 21:16
imagesJson = submitted.imagesJson,
filesJson = submitted.filesJson,
)
},

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.

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.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 172be5e. Configure here.

binding.inputField.setSelection(viewState.prompt.length)
} else {
clearInputField()
}

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.

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.

Fix in Cursor Fix in Web

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()

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.

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.

Fix in Cursor Fix in Web

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()

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.

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.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 172be5e. Configure here.

malmstein and others added 5 commits July 3, 2026 14:41
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>
@malmstein malmstein force-pushed the 07-02-uti_add_to_contextual_sheet branch from 172be5e to 80b3243 Compare July 3, 2026 12:42

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

There are 5 total unresolved issues (including 4 from previous reviews).

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 80b3243. Configure here.

} else {
// Chat already in progress: submit into the running chat via the JS event.
sendPrompt(prompt, modelId, reasoningEffort, selectedTool, imagesJson, filesJson)
}

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.

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.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 80b3243. Configure here.

val selectedTool: String?,
val imagesJson: JSONArray?,
val filesJson: JSONArray?,
)

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.

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 = {},

@joshliebe joshliebe Jul 3, 2026

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.

Would onNewPromptSubmitted be a better name?


renderPageContext(viewState.contextTitle, viewState.contextUrl, viewState.tabId)

if (viewState.quickActionState == DuckChatContextualViewModel.QuickActionState.ASK_ABOUT_PAGE) {

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.

nit: I think a switch would be more readable here

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

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.

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