Add native sync queue for Duck.ai chat updates#8640
Merged
Conversation
5568bdf to
6112df7
Compare
…-null, edit_timestamp)
Server-side ai_chats expects title encrypted via JWE (per-purpose protected key), not SyncCrypto. Native lacks that infra today, so PATCHing a SyncCrypto-encoded title would corrupt server state for the FE. Drop title from outgoing update entries and stop triggering sync on rename. Pin/unpin still propagate (plain wire format). Add ChatHistoryReader: loads Duck.ai in a hidden WebView on chat history screen onResume so the FE can fetch /sync/ai_chats, decrypt JWE titles, and push state into native Room via the duckAiNativeStorage.putChat bridge.
The subscriptions bridge wiring and the post-load settle delay were inherited from the visible Duck.ai fragment but neither serves the receive-side chat-sync flow — drop both. The settle delay also wasn't load-bearing once the fragment owns the WebView lifetime: the SPA's putChat fan-out continues after refresh() returns and the Room flow emits as writes land. Wire tearDown() in ChatHistoryFragment.onDestroyView so the WebView's destroy() is called exactly once when the screen exits.
Add DuckChatFeature.renameChat() (default false) and use it to show/hide the Rename row in the chat-history overflow popup. The toggle is read off the main thread each time the popup is opened so remote-config changes take effect on the next overflow tap without restarting the fragment.
The renameChat path no longer triggers sync since the title can't be sent without JWE. The two tests asserting the old sync interaction were left behind from the prior commit — remove them. The success and failure paths are still covered by the two tests just above.
The encryption hook was injected to wrap the chat title for outgoing PATCHes, but the title was dropped from the wire payload when rename sync was deferred to the JWE work. SyncCrypto wraps with the sync master key — the wrong algorithm for ai_chats titles anyway — so the follow-up rename work will bring its own primitives. Remove the constructor param, the test mock, the unused stub, and the orphaned imports.
6112df7 to
810c009
Compare
CDRussell
reviewed
May 22, 2026
CDRussell
approved these changes
May 22, 2026
Match the established codebase convention for headless WebViews used for FE-to-native communication: PirDetachedWebViewProvider, RealDuckChatDeleter, and RealChatSuggestionsReader all hold a WebView(context) in a private field with no view-hierarchy attachment. The earlier attached-INVISIBLE shape was an unverified hypothesis that the SPA needed a real laid-out window to trigger chat-list hydration — never confirmed by a putChat observation. Drop the Activity cast, the findViewById(android.R.id.content) lookup, addView/visibility setup, and the removeView line in tearDown. Tightens getOrCreateWebView's return type since the only nullable paths are gone.
DuckChatDataClearingPlugin.deleteAllChats() was clearing the pendingChatDeletions queue but not the new pendingChatUpdates queue, so stale pin/unpin update entries would survive a fire-all indefinitely. Add the symmetric clearPendingChatUpdates() call inside the same successful-deletion branch, and lock the behaviour in with matching positive and negative test assertions.
Contributor
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
❌ 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 e732534. Configure here.
runCatching catches every Throwable including CancellationException, which resets the coroutine's cancellation state and breaks structured concurrency — a child coroutine that was supposed to be cancelled silently keeps running. Switch to the same try / rethrow-cancellation / catch-exception cascade RenameChatViewModel uses in this module.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.


Task/Issue URL: https://app.asana.com/1/137249556945/project/481882893211075/task/1214962880887900?focus=true
Description
Wires Duck.ai chat pin/unpin into the existing native sync pipeline so the pinned state propagates to the user's other signed-in devices, and introduces a headless
ChatHistoryReaderso the chat-history screen picks up changes made elsewhere.The rename UI is gated behind a new remote feature flag (
renameChat, default OFF) — when enabled it persists locally, but cross-device propagation is intentionally deferred until JWE encryption lands on native (server requires titles to be JWE-encrypted, which native cannot produce today).Steps to test this PR
Note
Prerequisites:
UI changes
Screen.Recording.2026-05-21.at.15.12.46.mov
Note
Medium Risk
Medium risk because it changes the sync patch payload/queueing logic and adds a headless WebView-based refresher that could affect performance and sync correctness.
Overview
Adds a new native sync queue for Duck.ai chat updates (separate from deletions) and extends sync patch generation to send pinned-state changes, prioritizing deletions when a chatId is queued for both.
Pins/unpins now enqueue an update and trigger sync, bulk chat clearing also clears both deletion and update queues, and patch success removes acknowledged ids from both queues. The chat history screen additionally refreshes via a new headless
ChatHistoryReader, and the per-row Rename action is now gated behind a new remote flagrenameChat(default OFF).Reviewed by Cursor Bugbot for commit d6eef35. Bugbot is set up for automated code reviews on this repo. Configure here.