Skip to content

Ask the server only for the conversations that changed - #6744

Draft
AndyScherzinger wants to merge 5 commits into
masterfrom
feat/noid/conversation-list-delta-sync
Draft

AndyScherzinger wants to merge 5 commits into
masterfrom
feat/noid/conversation-list-delta-sync

Conversation

@AndyScherzinger

@AndyScherzinger AndyScherzinger commented Sep 21, 2026 •

Copy link
Copy Markdown
Member

Stack — review and merge bottom-up. Each PR targets the branch below it, so it shows its parents' commits until they merge.

  1. #6744 — modifiedSince delta sync ← you are here
  2. #6745 — periodic background refresh
  3. #6746 — foreground refresh loop
  4. #6763 — prefetch marking + mobile-preload-chat capability

Every conversation list sync is a full GET /room today: every room, with its last message. This adds the server's modifiedSince delta so a sync carries only what changed. It is the prerequisite for the two periodic triggers stacked on top of it, which would otherwise multiply the most expensive request the client makes.

What it does

  • Sends modifiedSince on GET /room and echoes back the X-Nextcloud-Talk-Modified-Before response header on the next request — the server's own timestamp, never a client clock.
  • Drops includeStatus on delta syncs, which otherwise makes the server return every one-to-one room regardless and eats most of the saving.
  • Still forces a full sync every 5 minutes, on internal signaling, on pull-to-refresh, on account switch and after a failed sync, per the server's guidance in docs/conversation.md.
  • Sends nothing on servers below conversation API v4, which answer in full.

The one thing to check in review

A delta response must never reach determineLeftConversationIds. That function lists every previously known conversation absent from the response — on a filtered response, nearly all of them — and the delete cascades through the foreign key to cached messages and chat blocks. RoomListResult.wasDelta is reported by the network layer rather than derived from the caller's intent, because an old server silently answers a delta request in full, and the reconcile is skipped on it.

Not in this PR

  • Periodic background refresh, and the foreground refresh loop while the list is open — stacked on top of this branch.
  • A full refresh on delete / disinvite signaling messages, which the server also recommends. Android handles neither event today.

🚧 TODO

  • Manual check against a real server: federated conversations are not skipped by delta syncs

🏁 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 Sep 21, 2026 •

Copy link
Copy Markdown
Contributor

📱 QA build

Download app-qa-debug.apk
QR code Open the QR code for this download
Commit 89b3874
Version 6744
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.

@AndyScherzinger AndyScherzinger added this to the 25.1.0 milestone Sep 21, 2026
@AndyScherzinger AndyScherzinger added the 2. developing Work in progress label Sep 21, 2026
@AndyScherzinger
AndyScherzinger force-pushed the feat/noid/conversation-list-delta-sync branch 5 times, most recently from 0b93e1e to 465e035 Compare September 23, 2026 12:26
@AndyScherzinger
AndyScherzinger force-pushed the feat/noid/conversation-list-delta-sync branch 5 times, most recently from 144cd33 to f59604f Compare September 24, 2026 14:42
Add the modifiedSince query parameter to GET /room and return the full
response so the X-Nextcloud-Talk-Modified-Before header can be read. The
network data source now reports the conversations, the timestamp to use
for the next delta request, and whether the response it got back was a
delta at all - a server below conversation API v4 answers in full
whatever it was asked, and a caller that assumed otherwise would
reconcile deletions against the wrong kind of response.

Assisted-by: Claude Code:claude-opus-5
Signed-off-by: Andy Scherzinger <info@andy-scherzinger.de>
Send the timestamp the server handed back on the last conversation list
sync as modifiedSince, so a sync carries what changed rather than every
room with its last message, and ask without includeStatus while doing
so - with it the server returns every one-to-one room whatever
modifiedSince says, which is most of the list on a typical account.

The response to such a request cannot express a conversation being
deleted or the user being removed from one, so the left-conversation
reconcile is skipped for it: every conversation the response leaves out
would otherwise look like one that was left, and deleting those cascades
through the foreign key to their cached messages and chat blocks. The
network layer reports whether the response really was a delta, because a
server below conversation API v4 answers in full whatever it was asked.

The timestamp is stored per account in arbitrary storage, and only once
the response is in the database - one kept ahead of a write that then
failed would permanently skip the conversations that write was carrying.
A failed sync drops it, so the next one asks for everything again.

Assisted-by: Claude Code:claude-opus-5
Signed-off-by: Andy Scherzinger <info@andy-scherzinger.de>
A modifiedSince response cannot say that a conversation was deleted or
that the user was removed from one, so the server asks clients to fetch
the whole list regularly anyway. Follow that: at least every five
minutes, and always when the internal signaling backend is in use, where
no signaling server exists to announce the change out of band.

A caller can also demand one. Pull to refresh does, so a conversation
left on another device is gone by the time the indicator stops spinning
rather than within the next five minutes, and so does the first sync
after an account switch or a process start, which has nothing this
process has seen the full picture behind to anchor a delta on.

A device clock that moved backwards makes the last full sync read as
being in the future; that is not a young full sync but an unusable one,
and it asks for a full one too.

Assisted-by: Claude Code:claude-opus-5
Signed-off-by: Andy Scherzinger <info@andy-scherzinger.de>
@AndyScherzinger
AndyScherzinger force-pushed the feat/noid/conversation-list-delta-sync branch from f59604f to 79b44ce Compare September 24, 2026 16:32
The case worth a test here is the one that destroys data rather than the
one that annoys: a delta response lists only what changed, so every
conversation it leaves out looks like one the user left, and reconciling
those away takes their cached messages and chat blocks with them through
the foreign key cascade.

The first test seeds ten conversations with cached chat, runs a delta
sync that mentions one of them, and asserts the other nine still have
theirs. It was checked against the mistake it guards: with the delta
branch removed from the sync, it fails.

The rest cover what decides the mode - the stored timestamp is sent and
includeStatus is not, the internal signaling backend never gets a delta,
a full response still reconciles a conversation away, and a failed sync
drops the timestamp so the next one asks for everything.

Assisted-by: Claude Code:claude-opus-5
Signed-off-by: Andy Scherzinger <info@andy-scherzinger.de>
The per-room message catch-up wraps each room in runCatching, which also
catches the CancellationException raised when the sync is stopped. The
failure was logged and the loop moved on to the next room, so a sync that
had been cancelled kept issuing requests for every remaining room.

Rethrow the cancellation so the loop ends with the scope that owns it.

Assisted-by: Claude Code:claude-opus-5
Signed-off-by: Andy Scherzinger <info@andy-scherzinger.de>
@AndyScherzinger
AndyScherzinger force-pushed the feat/noid/conversation-list-delta-sync branch from 79b44ce to 89b3874 Compare September 24, 2026 16:59
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.

1 participant