Skip to content

fix(chat): keep the reply count of a thread up to date - #7034

Open
AndyScherzinger wants to merge 2 commits into
masterfrom
fix/thread-replies-and-own-reaction-unread
Open

AndyScherzinger wants to merge 2 commits into
masterfrom
fix/thread-replies-and-own-reaction-unread

Conversation

@AndyScherzinger

@AndyScherzinger AndyScherzinger commented Nov 12, 2026 •

Copy link
Copy Markdown
Member

The "x replies" button of a thread in a conversation showed a stale reply count (e.g. 7 instead of 23), while the server and the web client had the correct one.

The count is taken from the cached root message of the thread, and that row only changed when the root message itself was fetched again. The server sends the thread's current reply count with every message of the thread (chat API and chat relay alike), but it was only stored on the reply itself. Replies fetched by the background prefetch or while the chat was open therefore never reached the button, even though opening the thread showed all of them.

This PR fixes it in two places:

  • When messages are stored (ChatMessagesDao.upsertChatMessagesAndDeleteTemp, the path of HTTP polling, signaling and the background prefetch), the reply count of the newest message of each thread is copied onto the cached root message.
  • Counts that are already stale in the cache are not touched by that, as no new message of the thread may arrive. Opening the main chat therefore also fetches the recently active threads of the conversation (threads/recent, limited to the server maximum of 50) and writes their reply counts into the cached roots, the way the web client takes them from its thread list. It only runs when the server has the threads capability.

In the long run we might want to move the thread count refresh over to the insurance request cycle (INSURANCE_REQUEST_DELAY in OfflineFirstChatRepository.kt), but for a swift cleanup it might be worth keeping it as done in this PR.

Verified on a device against cloud.nextcloud.com: the thread that showed 7 replies shows 23 after opening the chat.

🏁 Checklist

  • ⛑️ Tests (unit and/or integration) are included or not needed
  • 🔖 Capability is checked or not needed
  • 🔙 Backport requests are created or not needed: /backport to stable-xx.x
  • 📅 Milestone is set
  • 🌸 PR title is meaningful (if it should be in the changelog: is it meaningful to users?)

🤖 AI (if applicable)

  • The content of this PR was partly or fully generated using AI

@github-actions

github-actions Bot commented Nov 12, 2026 •

Copy link
Copy Markdown
Contributor

📱 QA build

Download app-qa-debug.apk
QR code Open the QR code for this download
Commit d450b54
Version 7034
Available until 7 days after this build

The QA build installs alongside a released Nextcloud app, so you can keep
using your existing install while testing.

Downloading the file requires a GitHub account, so open this link on the
device you want to test on, or transfer the APK to it.

@mahibi

mahibi commented Nov 12, 2026

Copy link
Copy Markdown
Collaborator

Thank you Andy 👍 , iirc i implemented the counting locally and afterwards it was implemented that the server returns it.

@AndyScherzinger
AndyScherzinger marked this pull request as ready for review November 12, 2026 11:25
@AndyScherzinger

Copy link
Copy Markdown
Member Author

Happy to help @mahibi - we can go either way. Currently it is broken, since it is cached in the DB but never updated, so it turns stale after initial save...

So could go in as is and either stay or be replaced later on with local counting, whatever you prefer

@AndyScherzinger AndyScherzinger added the bug Something isn't working label Nov 12, 2026
@AndyScherzinger AndyScherzinger added this to the 26.0.0 milestone Nov 12, 2026
@AndyScherzinger

Copy link
Copy Markdown
Member Author

/backport to stable-25.1.x

@mahibi

mahibi commented Nov 12, 2026

Copy link
Copy Markdown
Collaborator

I remembered that with removing the local solution some more code should be able to be removed. Claude confirms (but i did not verify yet):

The local reply counting from #(show number of thread replies, 0d919a5) is now fully superseded by the server-provided counts, and its remaining pieces are already dead code on master. The view holders and the ChatViewModel wrapper that called it were removed during the Compose/ExoPlayer migration, so nothing calls it anymore. This PR seems like a good place to remove the rest:

ChatMessagesDao.getNumberOfThreadReplies(...), the SELECT COUNT(*) query
ChatMessageRepository.getNumberOfThreadReplies(threadId)
OfflineFirstChatRepository.getNumberOfThreadReplies(...), the override
DummyChatMessagesDaoImpl.getNumberOfThreadReplies(...) in ComposePreviewUtilsDaos.kt

There are no other references in main, test or androidTest.

Optional: OfflineFirstChatRepository.updateThreadReplies runs one UPDATE per thread, up to 50. Moving that loop into a @transaction method on the DAO, the same way upsertChatMessagesAndDeleteTemp works, would batch it into a single transaction and a single Room invalidation.

@AndyScherzinger
AndyScherzinger force-pushed the fix/thread-replies-and-own-reaction-unread branch from 27e90f4 to c4c9236 Compare November 12, 2026 14:35
The "x replies" label of a thread root is read from the cached root
message, which only changed when the root message itself was fetched
again. The server sends the current reply count of the thread with
every message of the thread, over the chat API and the chat relay
alike, but it was only stored on the reply itself. Replies fetched by
the background prefetch or while the chat was open therefore never
reached the label.

When messages are stored, copy the reply count of the newest message
of each thread onto the cached root message. Every sync path stores
messages through upsertChatMessagesAndDeleteTemp, so HTTP polling,
signaling and the background prefetch are all covered.

Counts that are already stale in the cache are not touched by that, as
no new message of the thread may arrive. Opening the main chat
therefore also fetches the recently active threads of the conversation
and writes their reply counts into the cached roots, the way the web
client takes them from its thread list.

Assisted-by: Claude Code:claude-opus-5-5
Signed-off-by: Andy Scherzinger <info@andy-scherzinger.de>
The reply count of a thread is taken from the server, so the local
count of replies by parent message is no longer used anywhere. Remove
the query and its repository and preview wrappers.

Assisted-by: Claude Code:claude-opus-5-5
Signed-off-by: Andy Scherzinger <info@andy-scherzinger.de>
@AndyScherzinger
AndyScherzinger force-pushed the fix/thread-replies-and-own-reaction-unread branch from c4c9236 to d450b54 Compare November 13, 2026 08:50
@AndyScherzinger

Copy link
Copy Markdown
Member Author

@mahibi thanks, both done and pushed:

  • Verified the dead code first: getNumberOfThreadReplies had no callers left in main, test or androidTest, only the declaration, the two overrides and the delegation. Removed all four in d450b54 (refactor(chat): remove the unused local thread reply counting).
  • The batching went into the fix commit itself (47671e9): ChatMessagesDao.updateThreadRepliesOfThreads is a @Transaction method like upsertChatMessagesAndDeleteTemp, so the up to 50 counts from threads/recent are written in one transaction with one Room invalidation. Added a DAO test for writing several threads at once.

The branch is rebased onto current master; the thread tests and detekt ktlintCheck pass.

@github-actions

Copy link
Copy Markdown
Contributor

Codacy

Lint

TypemasterPR
Warnings1716
Errors00

SpotBugs

CategoryBaseNew
Bad practice77
Correctness88
Dodgy code4040
Internationalization33
Malicious code vulnerability33
Performance77
Security1111
Total7979

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants