Commit 20310fd
Add ActivePlugin point for Chat-tab contributions (#8948)
Task/Issue URL:
https://app.asana.com/1/137249556945/project/1214157224317277/task/1215897994835019
Tech Design URL (if applicable):
https://app.asana.com/1/137249556945/task/1215906519502283
### Description
Introduces `NativeInputChatTabItemPlugin`, an `ActivePluginPoint` that
lets modules outside `duckchat-impl` contribute their own item(s) to the
native-input Chat-tab suggestions list (the list shown under the
**Chat** tab when the input is focused in *Search & Duck.ai* mode),
gated by remote config and without depending on `duckchat-impl`.
The full design lives in the [Tech
Design](https://app.asana.com/1/137249556945/task/1215906519502283).
Highlights:
- **API (`duckchat-api`):** `NativeInputChatTabItemPlugin` is a factory
for a per-binding `NativeInputChatTabItem`, which owns a
`RecyclerView.Adapter` slotted into the existing Chat-tab
`ConcatAdapter`. A plugin declares via `supportsQuery` whether it
participates in filtering: `false` items are zero-state (shown only
while the query is empty, hidden once the user types); `true` items stay
and receive `onQueryChanged`. The interface lives in `duckchat-api` so
it travels with native input when it moves to its own module.
- **Plugin point (`duckchat-impl`):**
`pluginPointNativeInputChatTabItemPlugin` (default enabled); each
contributed plugin is additionally gated by its own
`@ContributesActivePlugin` flag. Ordering comes from
`@ContributesActivePlugin(priority = …)`.
- **Host (`duckchat-impl`):** `NativeInputChatSuggestionsBinder` loads
the enabled plugins and inserts each item's adapter at the **top** of
the `ConcatAdapter` in plugin-point order, above the built-in sections
(which are left untouched). `submit()` forwards the query only to
query-aware items and folds visible plugin content into the overlay's
`hasContent`. Loading is decoupled from `submit`, so the latest `submit`
is replayed once plugins finish loading. `NativeInputModeWidget` owns a
per-binding `CoroutineScope` handed to each item, cancelled at teardown.
This is the first step toward later migrating the existing sections
(chat history, "View all Chats", URL suggestions, "Search for …") onto
the same plugin point — see the Tech Design.
**No user-facing change:** this PR adds the mechanism only; there are no
contributors yet (an internal-only example card was used to validate the
contract during development and has been removed).
### Steps to test this PR
_Plugin mechanism (no user-visible change)_
- [ ] `./gradlew :duckchat-impl:testDebugUnitTest --tests
"com.duckduckgo.duckchat.impl.ui.nativeinput.views.NativeInputChatSuggestionsBinderTest"`
passes — covers top insertion in priority order, query forwarding only
to `supportsQuery == true` items, non-query items hidden while typing
and restored on clear, `hasContent` aggregation, and the load-vs-submit
race (replay).
- [ ] App builds and the Chat tab behaves exactly as before, since no
module contributes a plugin yet.
### UI changes
| Before | After |
| ------ | ----- |
| _No UI changes — mechanism only, no contributors yet_ | |
<!-- CURSOR_SUMMARY -->
---
> [!NOTE]
> **Medium Risk**
> Touches native input overlay visibility and keystroke-driven state;
behavior is heavily unit-tested but affects a core Duck.ai input UX
path.
>
> **Overview**
> Adds an **ActivePlugin** extension point so other modules can
contribute rows to the native-input **Chat** tab suggestions list
without depending on `duckchat-impl`. The API exposes
`NativeInputChatTabItemPlugin` / `NativeInputChatTabItem`, plus
`SingleViewChatTabItem` for single-card contributions, and extends
**`DuckChatInputModeState`** with a **`chatQuery`** `StateFlow` (kept in
sync from `InputModeWidget` via **`setChatQuery`**).
>
> The host **`NativeInputChatSuggestionsBinder`** loads enabled plugins
into the top of the existing **`ConcatAdapter`**, observes adapter
changes to recompute overlay **`hasContent`** (using live
**`chatQuery`** so zero-state cards can hide without flickering the
overlay closed while the user types), and **`NativeInputModeWidget`**
owns a scoped coroutine job for plugin loading/teardown. An
internal-only **`ExampleMessageCardPlugin`** demonstrates the pattern;
unit tests cover ordering, overlay aggregation, load-after-submit,
dismiss, and clear races.
>
> <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit
9ad5e42. 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: Claude Opus 4.8 <noreply@anthropic.com>1 parent 282dce1 commit 20310fd
14 files changed
Lines changed: 761 additions & 17 deletions
File tree
- duckchat
- duckchat-api
- src
- main/java/com/duckduckgo/duckchat/api
- inputscreen
- test/kotlin/com/duckduckgo/duckchat/api/inputscreen
- duckchat-impl/src
- main
- java/com/duckduckgo/duckchat/impl
- inputscreen/ui
- suggestions
- example
- view
- ui/nativeinput/views
- res/values
- test/kotlin/com/duckduckgo/duckchat/impl
- messaging/fakes
- ui/nativeinput/views
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
34 | 34 | | |
35 | 35 | | |
36 | 36 | | |
| 37 | + | |
37 | 38 | | |
38 | 39 | | |
39 | 40 | | |
| |||
Lines changed: 10 additions & 0 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
35 | 35 | | |
36 | 36 | | |
37 | 37 | | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
38 | 48 | | |
39 | 49 | | |
40 | 50 | | |
| |||
Lines changed: 78 additions & 0 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
Lines changed: 92 additions & 0 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
| 84 | + | |
| 85 | + | |
| 86 | + | |
| 87 | + | |
| 88 | + | |
| 89 | + | |
| 90 | + | |
| 91 | + | |
| 92 | + | |
Lines changed: 82 additions & 0 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
Lines changed: 14 additions & 0 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
126 | 126 | | |
127 | 127 | | |
128 | 128 | | |
| 129 | + | |
| 130 | + | |
| 131 | + | |
| 132 | + | |
| 133 | + | |
| 134 | + | |
| 135 | + | |
129 | 136 | | |
130 | 137 | | |
131 | 138 | | |
| |||
397 | 404 | | |
398 | 405 | | |
399 | 406 | | |
| 407 | + | |
400 | 408 | | |
401 | 409 | | |
402 | 410 | | |
| |||
827 | 835 | | |
828 | 836 | | |
829 | 837 | | |
| 838 | + | |
| 839 | + | |
| 840 | + | |
| 841 | + | |
| 842 | + | |
| 843 | + | |
830 | 844 | | |
831 | 845 | | |
832 | 846 | | |
| |||
Lines changed: 33 additions & 0 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
0 commit comments