Skip to content

Refresh conversations in the background so they are not stale when the app is opened - #6745

Merged
AndyScherzinger merged 11 commits into
masterfrom
feat/noid/conversation-list-background-sync
Oct 6, 2026
Merged

AndyScherzinger merged 11 commits into
masterfrom
feat/noid/conversation-list-background-sync

Conversation

@AndyScherzinger

@AndyScherzinger AndyScherzinger commented Sep 21, 2026 •

Copy link
Copy Markdown
Member

Stack — review and merge bottom-up. #6744 is merged, so #6745 now targets master directly; #6746 and #6763 still target the branch below them and show their parents' commits until those merge.

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

A backgrounded app learns nothing about its conversations until a push arrives. Where push is disabled, unavailable, or simply does not turn up, the list, the unread counts and the cached messages stay as they were until the user opens the app. This adds the periodic background refresh iOS has had all along.

What it does

  • A suspend sync entry point on the repository that completes when the sync and its message catch-up are done. The existing getRooms launches into the repository's own scope and returns immediately, which is what the list wants and what a worker cannot use — WorkManager tears the process down the moment the worker returns.
  • ConversationsSyncWorker, a 15-minute periodic worker, syncing every configured account one after another rather than at once.
  • Stands down in battery saver and while the app is in the foreground, where the list refreshes itself.

What a reviewer should push back on

  • It bounds staleness, it does not deliver immediacy. WorkManager's period floor is 15 minutes and Doze defers further, so this is coarser than the iOS background refresh it mirrors.
  • No isOnline pre-check, deliberately — the connectivity flow is frozen in a process with no UI collector, which is what silently disabled the message prefetch before (fix(chat): make the push message prefetch actually run on a flaky connection #6727). Reachability is the work request's NetworkType.CONNECTED constraint.
  • All accounts per wake-up, uncapped. Marked with a ponytail: comment naming the ceiling. The delta sync from Ask the server only for the conversations that changed #6744 is what makes this affordable; cap the run and take the stalest accounts first if request volume ever shows it.

Not in this PR

  • The foreground refresh loop while the list is open — stacked on top of this branch.
  • The notification-existence check and offline-message resend that iOS also does on its background task.

🚧 TODO

  • Manual check: three accounts, confirm one wake-up does not burst requests
  • Overnight battery attribution run

🏁 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

@AndyScherzinger AndyScherzinger added this to the 25.1.0 milestone Sep 21, 2026
@AndyScherzinger
AndyScherzinger force-pushed the feat/noid/conversation-list-delta-sync branch from bb53fe7 to c0b96dd Compare September 21, 2026 20:23
@AndyScherzinger
AndyScherzinger force-pushed the feat/noid/conversation-list-background-sync branch from abc0d72 to 7d19a5c Compare September 21, 2026 20:23
@AndyScherzinger
AndyScherzinger force-pushed the feat/noid/conversation-list-delta-sync branch from c0b96dd to d4f1b1b Compare September 22, 2026 14:16
@AndyScherzinger
AndyScherzinger force-pushed the feat/noid/conversation-list-background-sync branch from 7d19a5c to f92c673 Compare September 22, 2026 14:16
@AndyScherzinger
AndyScherzinger force-pushed the feat/noid/conversation-list-delta-sync branch from d4f1b1b to a633b9c Compare September 22, 2026 14:52
@AndyScherzinger
AndyScherzinger force-pushed the feat/noid/conversation-list-background-sync branch from f92c673 to 523a0fa Compare September 22, 2026 14:52
@AndyScherzinger
AndyScherzinger force-pushed the feat/noid/conversation-list-delta-sync branch from a633b9c to 0b93e1e Compare September 23, 2026 09:21
@AndyScherzinger
AndyScherzinger force-pushed the feat/noid/conversation-list-background-sync branch from 523a0fa to 6621cf4 Compare September 23, 2026 09:21
@AndyScherzinger
AndyScherzinger force-pushed the feat/noid/conversation-list-delta-sync branch from 0b93e1e to 465e035 Compare September 23, 2026 12:26
@AndyScherzinger
AndyScherzinger force-pushed the feat/noid/conversation-list-background-sync branch from 6621cf4 to 76d0e52 Compare September 23, 2026 12:26
@AndyScherzinger
AndyScherzinger force-pushed the feat/noid/conversation-list-delta-sync branch from 465e035 to c7917bd Compare September 23, 2026 14:21
@AndyScherzinger
AndyScherzinger force-pushed the feat/noid/conversation-list-background-sync branch from 76d0e52 to fbd79d1 Compare September 23, 2026 14:21
@AndyScherzinger
AndyScherzinger force-pushed the feat/noid/conversation-list-delta-sync branch from c7917bd to 2b6b3b8 Compare September 23, 2026 15:11
@AndyScherzinger
AndyScherzinger force-pushed the feat/noid/conversation-list-background-sync branch from fbd79d1 to 9b7b1a6 Compare September 23, 2026 15:12
@AndyScherzinger
AndyScherzinger force-pushed the feat/noid/conversation-list-delta-sync branch from 2b6b3b8 to 0cdcc22 Compare September 24, 2026 06:09
@AndyScherzinger
AndyScherzinger force-pushed the feat/noid/conversation-list-background-sync branch from 9b7b1a6 to d9a67e2 Compare September 24, 2026 06:09
@AndyScherzinger
AndyScherzinger force-pushed the feat/noid/conversation-list-delta-sync branch from 0cdcc22 to 144cd33 Compare September 24, 2026 08:29
@AndyScherzinger
AndyScherzinger force-pushed the feat/noid/conversation-list-background-sync branch from d9a67e2 to ae967b3 Compare September 24, 2026 08:29
@AndyScherzinger
AndyScherzinger force-pushed the feat/noid/conversation-list-delta-sync branch from 144cd33 to f59604f Compare September 24, 2026 14:42
@AndyScherzinger
AndyScherzinger force-pushed the feat/noid/conversation-list-background-sync branch from ae967b3 to 3da4c9f Compare September 24, 2026 14:42
@AndyScherzinger
AndyScherzinger force-pushed the feat/noid/conversation-list-delta-sync branch from f59604f to 79b44ce Compare September 24, 2026 16:32
@AndyScherzinger
AndyScherzinger force-pushed the feat/noid/conversation-list-background-sync branch from 3da4c9f to 9596d4e Compare September 24, 2026 16:33
@AndyScherzinger
AndyScherzinger force-pushed the feat/noid/conversation-list-delta-sync branch from 79b44ce to 89b3874 Compare September 24, 2026 16:59
@AndyScherzinger
AndyScherzinger force-pushed the feat/noid/conversation-list-background-sync branch from 9596d4e to b9aa2b8 Compare September 24, 2026 16:59
@AndyScherzinger
AndyScherzinger force-pushed the feat/noid/conversation-list-delta-sync branch from 0a1d95f to 6de9ce9 Compare October 4, 2026 08:35
@AndyScherzinger
AndyScherzinger force-pushed the feat/noid/conversation-list-background-sync branch from e74c3a1 to 81d9079 Compare October 4, 2026 08:35
Base automatically changed from feat/noid/conversation-list-delta-sync to master October 5, 2026 22:02
@coderabbitai

coderabbitai Bot commented Oct 5, 2026 •

Copy link
Copy Markdown

Review in Change Stack →

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • ✅ Review completed - (🔄 Check again to review again)
📝 Walkthrough

Walkthrough

The repository adds a suspending operation for conversation room-list sync and a separate operation for message catch-up. A periodic worker syncs configured accounts, with battery-saver and foreground checks, retry handling, and connected-network scheduling. The application schedules the worker. A shared context extension provides the battery-saver check used by both workers. Tests cover repository behavior, worker outcomes, cancellation, and rescheduling.

Priority: ➖ Normal

Merge Risk: 🟡 Moderate · up to ca489

A failure reading the sync timestamps can skip every account's background refresh without any retry. Accounts with slow syncs may also be cancelled on every run and stay stale. Add a fallback for the timestamp read before merging.

Security Architecture Review

Security architecture risk: 🟡 Moderate · up to 911db

Background refresh uses existing account permissions, but queued synchronization can race account removal, and message catch-up failures can be reported as successful completion. The exposure is bounded to accounts configured on the device; no new external entrypoint or elevated permission was identified.

Retained concerns

  • Medium · security · inferred: The new multi-account worker loads eligible accounts once, then retains their credential-bearing snapshots while preceding accounts synchronize. If an account is removed during that interval, a later sync can still initiate authenticated requests using the retained snapshot: neither the per-account call nor repository catch-up rechecks current removal eligibility. Account removal does not coordinate cancellation of this worker in the inspected removal path. This expands the queued-work race beyond caller-driven refresh, although related in-flight credential use predates the PR. Subsequent runs exclude removed accounts, and cascading foreign keys constrain persistence after deletion; cross-account access or durable resurrection was not established.
  • Medium · reliability · observed: The new awaited API exposes completion and failure semantics that the reused best-effort catch-up implementation does not guarantee. Room state and synchronization timestamps advance before catch-up; per-room exceptions are logged, returned syncFailed outcomes are ignored, and an overlapping catch-up can return immediately after requesting a rerun. The worker can therefore report success without successful completion of all triggered catch-up. Cancellation propagates, but already-advanced room state can prevent unchanged-activity rooms with local chat blocks from being selected again. This materially weakens failure reporting and recovery for the new background lifecycle; it is not evidence of a verified security vulnerability.
Security review details

Security Blast Radius

  • inferred — The new lifecycle autonomously reaches every configured, eligible account using its existing credentials. Its relevant exposure is authenticated requests and local conversation/message state for those accounts, including separate configured servers. No new credential privilege, tenant impersonation mechanism, or remote invocation route was identified in the inspected change.

Security Findings and Attack Paths

  • inferred — The supported security concern is a lifecycle race, not a demonstrated attacker-controlled exploit: account removal can occur after enumeration but before a queued account starts, leaving the worker with credentials for an account no longer eligible locally. This can initiate post-removal authenticated work. Persistence after actual user deletion is constrained by foreign keys, and no cross-account disclosure was established.

Trust Boundaries and Controls

  • observed — Account enumeration excludes users scheduled for deletion. Conversation queries filter by account ID, message targets use account-qualified identities, and foreign keys cascade from users to conversations and from conversations to messages and chat blocks. These are concrete ownership and cleanup controls against ordinary cross-account mixing and orphaned persistent chat state.

Resilience and Maintainability Implications

  • observed — Same-room catch-up is coalesced under a mutex with finally-based release, and derived conversation updates reject older activity. However, coalescing returns immediately to overlapping callers rather than joining completion, and the foreground guard is only checked at worker initiation. These controls do not establish exclusive ownership of the complete foreground/background synchronization transition.

Hardening Proposals

  • proposed — Define account-removal ownership for background synchronization: revalidate eligibility before queued accounts start and coordinate removal with cancellation or account-generation invalidation. Revalidation alone should not be treated as eliminating an already-running request race.
  • proposed — Make awaited completion distinguish room persistence from catch-up success, join overlapping catch-up when completion is promised, and preserve recoverable pending catch-up state across interruption. Coordinate per-account response application and timestamp updates so foreground/background overlap cannot silently weaken recovery.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 26.23% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 61 functions across 9 files. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly describes the main change: refreshing conversations in the background to keep them current when the app opens.
Description check ✅ Passed The description explains the change, limitations, scope, and completed checks. It includes the TODO, checklist, and AI sections; the template’s screenshots section is omitted, which is not critical fo…
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
  • Autopilot · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4


ℹ️ Review info
⚙️ Run configuration
  • Configuration used: Organization UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 6a83f7f9-a39c-47c6-a83f-2161bf361fbf
📥 Commits

Reviewing files that changed from the base of the PR and between b2001c5 and 81d9079.

📒 Files selected for processing (8)
  • app/build.gradle.kts
  • app/src/main/java/com/nextcloud/talk/application/NextcloudTalkApplication.kt
  • app/src/main/java/com/nextcloud/talk/conversationlist/data/OfflineConversationsRepository.kt
  • app/src/main/java/com/nextcloud/talk/conversationlist/data/network/OfflineFirstConversationsRepository.kt
  • app/src/main/java/com/nextcloud/talk/extensions/ContextExtensions.kt
  • app/src/main/java/com/nextcloud/talk/jobs/ChatMessageCatchUpWorker.kt
  • app/src/main/java/com/nextcloud/talk/jobs/ConversationsSyncWorker.kt
  • app/src/test/java/com/nextcloud/talk/jobs/ConversationsSyncWorkerTest.kt

Included review availability: This review used your included allowance. Your plan provides up to 4 included reviews per hour; 3 remain after this review.

Comment thread app/src/main/java/com/nextcloud/talk/jobs/ConversationsSyncWorker.kt Outdated
@AndyScherzinger
AndyScherzinger force-pushed the feat/noid/conversation-list-background-sync branch from 81d9079 to 40eb091 Compare October 5, 2026 22:31

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1


ℹ️ Review info
⚙️ Run configuration
  • Configuration used: Organization UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: eccaa627-cbd1-4b95-80dd-6cdbbf33577b
📥 Commits

Reviewing files that changed from the base of the PR and between 81d9079 and 40eb091.

📒 Files selected for processing (4)
  • app/src/main/java/com/nextcloud/talk/conversationlist/data/network/OfflineFirstConversationsRepository.kt
  • app/src/main/java/com/nextcloud/talk/jobs/ConversationsSyncWorker.kt
  • app/src/test/java/com/nextcloud/talk/conversationlist/data/network/OfflineFirstConversationsRepositoryTest.kt
  • app/src/test/java/com/nextcloud/talk/jobs/ConversationsSyncWorkerTest.kt

Included review availability: This review used your included allowance. Your plan provides up to 4 included reviews per hour; 1 remain after this review.

Comment thread app/src/main/java/com/nextcloud/talk/jobs/ConversationsSyncWorker.kt Outdated
@github-actions

github-actions Bot commented Oct 6, 2026 •

Copy link
Copy Markdown
Contributor

📱 QA build

Download app-qa-debug.apk
QR code Open the QR code for this download
Commit 298813b
Version 6745
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 force-pushed the feat/noid/conversation-list-background-sync branch 2 times, most recently from 911db4c to ffc197a Compare October 6, 2026 08:25

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2


ℹ️ Review info
⚙️ Run configuration
  • Configuration used: Organization UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: ff358494-90ec-4501-844e-9958d26aa0c4
📥 Commits

Reviewing files that changed from the base of the PR and between 911db4c and ffc197a.

📒 Files selected for processing (2)
  • app/src/main/java/com/nextcloud/talk/jobs/ConversationsSyncWorker.kt
  • app/src/test/java/com/nextcloud/talk/jobs/ConversationsSyncWorkerTest.kt

Included review availability: This review used your included allowance. Your plan provides up to 4 included reviews per hour; 3 remain after this review.

Comment thread app/src/main/java/com/nextcloud/talk/jobs/ConversationsSyncWorker.kt Outdated
Comment thread app/src/main/java/com/nextcloud/talk/jobs/ConversationsSyncWorker.kt Outdated
@AndyScherzinger
AndyScherzinger force-pushed the feat/noid/conversation-list-background-sync branch from ffc197a to 731fcc1 Compare October 6, 2026 09:01

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1


ℹ️ Review info
⚙️ Run configuration
  • Configuration used: Organization UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 4b31d7b8-532a-45a5-b243-37fe02def3b7
📥 Commits

Reviewing files that changed from the base of the PR and between ffc197a and 731fcc1.

📒 Files selected for processing (2)
  • app/src/main/java/com/nextcloud/talk/jobs/ConversationsSyncWorker.kt
  • app/src/test/java/com/nextcloud/talk/jobs/ConversationsSyncWorkerTest.kt

Included review availability: This review used your included allowance. Your plan provides up to 4 included reviews per hour; 2 remain after this review.

var failed = 0
for (user in longestWaitingFirst) {
val share = (deadline - SystemClock.elapsedRealtime()) / accountsLeft
if (!syncAccount(user, share.coerceAtLeast(MIN_ACCOUNT_BUDGET_MILLIS))) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | 🏗️ Heavy lift

Keep minimum account budgets within the run deadline.

If 21 accounts are configured and the first 20 each take at least 30 seconds, this minimum consumes ten minutes before account 21 starts. WorkManager then signals the worker to stop. If those attempts keep timing out, unchanged sync timestamps can put the same accounts first on every run. Limit the allocated budgets to the remaining run time, and preserve progress so accounts that were not attempted can run next time. (developer.android.com)

@github-actions

github-actions Bot commented Oct 6, 2026

Copy link
Copy Markdown
Contributor

Codacy

Lint

TypemasterPR
Warnings138138
Errors1815

SpotBugs

CategoryBaseNew
Bad practice77
Correctness1111
Dodgy code4040
Internationalization33
Malicious code vulnerability33
Performance88
Security1111
Total8383

@mahibi
mahibi force-pushed the feat/noid/conversation-list-background-sync branch from 731fcc1 to ca48909 Compare October 6, 2026 12:01

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1


ℹ️ Review info
⚙️ Run configuration
  • Configuration used: Organization UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 161b2d4f-0c8b-43c2-84a9-48ee677d7d73
📥 Commits

Reviewing files that changed from the base of the PR and between 731fcc1 and ca48909.

📒 Files selected for processing (5)
  • app/src/main/java/com/nextcloud/talk/conversationlist/data/OfflineConversationsRepository.kt
  • app/src/main/java/com/nextcloud/talk/conversationlist/data/network/OfflineFirstConversationsRepository.kt
  • app/src/main/java/com/nextcloud/talk/jobs/ConversationsSyncWorker.kt
  • app/src/test/java/com/nextcloud/talk/conversationlist/data/network/OfflineFirstConversationsRepositoryTest.kt
  • app/src/test/java/com/nextcloud/talk/jobs/ConversationsSyncWorkerTest.kt

Included review availability: This review used your included allowance. Your plan provides up to 4 included reviews per hour; 3 remain after this review.

Comment thread app/src/main/java/com/nextcloud/talk/jobs/ConversationsSyncWorker.kt Outdated
AndyScherzinger and others added 11 commits October 6, 2026 21:15
getRooms launches into the repository's own scope and returns straight
away, which is what the conversation list wants and what a background
worker cannot use: WorkManager tears the process down the moment the
worker returns, and the sync would be cut off mid-request.

Add a suspend entry point that completes when the sync and the message
catch-up it triggers are done. The catch-up is launched detached on the
existing path and awaited on this one, so a worker cannot report success
for messages it never fetched.

It deliberately leaves the observed account alone. That selects what the
conversation list screen shows, and a worker walking several accounts in
one run must not move it.

Assisted-by: Claude Code:claude-opus-5
Signed-off-by: Andy Scherzinger <info@andy-scherzinger.de>
A backgrounded app learns nothing about its conversations until a push
arrives. Where push is disabled, unavailable or simply does not turn up,
that means the list, the unread counts and the cached messages stay as
they were until the user opens the app.

Add a periodic worker that runs the conversation list sync and its
message catch-up for every configured account. Accounts are synced one
after another rather than at once, because each fans out into its own
bounded set of message requests and running them together multiplies
that into a burst on a single wake-up.

It stands down in battery saver and while the app is in the foreground,
where the list refreshes itself, and leaves reachability to the work
request's network constraint - the connectivity flow is frozen in a
process with no UI collector, and pre-checking it there is what silently
disabled the message prefetch before.

WorkManager's period floor is 15 minutes and Doze defers it further, so
this bounds staleness rather than delivering immediacy. Scheduling keeps
an existing run instead of replacing it, so an app opened often still
reaches one.

The battery saver check would have been copied a third time here, so it
moves to a Context extension the repository and both workers share. The
worker's decision is kept apart from doWork, which reaches for the
application singleton to inject itself and can therefore not be driven
from a unit test.

Assisted-by: Claude Code:claude-opus-5
Signed-off-by: Andy Scherzinger <info@andy-scherzinger.de>
What is worth pinning down about a background worker is when it stands
down and how many accounts it touches when it does not: a guard that
quietly stops working is invisible until it turns up as battery drain or
request volume, long after the change that broke it.

Covers battery saver standing the run down without reading a single
account, every configured account being synced rather than only the
current one, a failed account asking for another attempt while its
neighbours still sync, the attempt cap ending the retries, and an
account lookup that throws being survivable.

Unit tests gain the WorkManager testing artifact, which was available to
instrumented tests only.

Assisted-by: Claude Code:claude-opus-5
Signed-off-by: Andy Scherzinger <info@andy-scherzinger.de>
The background worker gave each account two minutes for its whole sync,
the message catch-up included. On a slow connection the catch-up alone
can take longer than that - up to twenty rooms, each needing requests
that may run into the 45 second HTTP timeouts - so an account whose room
list was already stored got reported as failed, and the run was retried
for every account.

Timing only the room list fixes that, but leaves the catch-up running
inline between accounts: one account's slow prefetch then delays the
room lists of every account behind it, and can use up the ten minutes
WorkManager grants a run before those are reached at all.

The room list is what matters - unread counts, new and left
conversations - while missed messages load anyway once a chat is
opened. So the worker now works in two passes: it syncs the room list of
every account first, under the per-account time budget, and only then
catches up messages with the time the run has left. A catch-up failure
is logged and no longer fails the run, since the list it follows is
already stored.

To allow that, syncRooms() no longer catches up itself: it returns the
rooms with new messages, or null when the sync failed, and the new
catchUpRooms() prefetches them. getRooms() keeps launching the catch-up
in the background, so the conversation list screen behaves as before.

Assisted-by: Claude Code:claude-opus-5
Signed-off-by: Marcel Hibbe <dev@mhibbe.de>
…Manager

The room list sync returned early whenever the network monitor did not
report the device as online. The conversation list screen checks that
before it syncs anyway, so the early return only ever affected the
background worker - the one caller that is meant to rely on its work
request's network constraint instead.

The two disagree on a network that is connected but not yet validated,
or sits behind a captive portal: WorkManager starts the worker, while
the monitor, which waits for validation, reports offline. Every account
then counted as failed and the run burned its retries without sending a
single request.

Send the request and let it fail like any other when the network really
is unusable, as ChatMessageSyncer already does for the same reason.

Assisted-by: Claude Code:claude-opus-5
Signed-off-by: Marcel Hibbe <dev@mhibbe.de>
… foreground

The worker checked whether the app was in the foreground only when a
run started. A run can take up to the ten minutes WorkManager grants
it, and opening the app in that time had the conversation list sync
the account it shows while the worker went on syncing the same account.
The two write the same rows, and a slower, older response landing last
put stale unread counts back until the next sync.

Check again before each account's room list, and leave the remaining
room lists to the next run once the app is in the foreground - the same
stand-down a run that starts there gets.

The message catch-up is not cut short with them. The run stored those
rooms' latest activity with their room list already, so the
conversation list's own sync no longer sees new messages in them and
would not catch them up in the run's place. It can safely run alongside
the screen: its update to a conversation is skipped when the stored
state is newer, and overlapping catch-ups of a room are coalesced.

A run that stops early leaves accounts untried, so its warning counts
failures against the accounts it tried rather than all of them.

The foreground guard at the start of a run was untested, while the
battery saver one next to it was covered. Both it and the app coming to
the foreground mid-run are now covered, driving the real process
lifecycle.

Assisted-by: Claude Code:claude-opus-5
Signed-off-by: Marcel Hibbe <dev@mhibbe.de>
…n list

A failed room list sync of an account without cached conversations is
reported on syncErrorFlow, so the conversation list can tell the user
why it stays empty. The flow does not say which account failed, and the
list's view model collects it for as long as it lives - in the
background too - and turns whatever arrives into an error dialog.

The background worker runs that same sync for every account. A 401 for
an account whose password was changed, or a server that is down, then
put an unauthorized or error dialog on the list of whichever account the
user had open when they came back.

Only the sync the conversation list starts for the account it shows
reports there now, as the flow's documentation already promises. The
worker learns of a failure through syncRooms' return value and logs it.

Assisted-by: Claude Code:claude-opus-5
Signed-off-by: Marcel Hibbe <dev@mhibbe.de>
…caller's thread

syncRooms blocks the thread it runs on: the request is awaited with
blockingSingle, retried up to three times with each attempt bounded only
by the 45 second HTTP timeouts, and the stored sync state is read with
blockingGet. getRooms never showed it, since it runs in the repository's
own IO scope, but syncRooms runs on its caller's thread - for the
background worker one of the few threads of Dispatchers.Default, sized
to the CPU cores and shared with the rest of the app's coroutines, which
a slow server then held for minutes. Called from the main thread it
would not even get that far: the request delivers its result on the main
thread that blockingSingle is blocking.

The worker's ordering of the accounts did the same on a smaller scale.
It sorted with sortedBy on the stored time of each account's last full
sync, and sortedBy calls its selector for every comparison rather than
once per account - each call a blocking database query.

Run syncRooms and lastFullSyncAt on the IO dispatcher, as
ConversationListUpdater does around its blocking calls, so both are
safe to call from any thread, and read each account's last full sync
once before sorting on the values.

Assisted-by: Claude Code:claude-opus-5
Signed-off-by: Marcel Hibbe <dev@mhibbe.de>
… keeping it

The periodic conversation sync was scheduled with KEEP, so that an app
started often does not restart the period every time and so never
reaches a run. KEEP also ignores the request it is handed, though: once
a version of the app has scheduled the worker, a later version that
changes its interval or constraints never gets them applied - existing
installs keep running the old request until the app data is cleared.

UPDATE keeps what KEEP was chosen for - the scheduled run and its
timing stay in place - but applies the new request to it, so changes
to the schedule reach existing installs.

Assisted-by: Claude Code:claude-opus-5
Signed-off-by: Marcel Hibbe <dev@mhibbe.de>
… be read

The worker orders the accounts by the time of their last full sync, read
from the database with a blocking query. Nothing caught a failure of that
read, so it escaped doWork and failed the run outright: no account was
synced and no retry was scheduled, all for an order that only exists to
keep slow accounts from starving the others.

Fall back to the accounts' own order when the read fails, and let
cancellation through as before.

Assisted-by: Claude Code:claude-opus-5
Signed-off-by: Marcel Hibbe <dev@mhibbe.de>
syncAccounts read the accounts, ordered them and synced them in one
function with three returns, over detekt's ReturnCount limit of two.

Move reading the accounts, ordering them and syncing them into functions
of their own, leaving syncAccounts a single when over the outcome of the
read. The behaviour is unchanged.

Assisted-by: Claude Code:claude-opus-5
Signed-off-by: Marcel Hibbe <dev@mhibbe.de>
@AndyScherzinger
AndyScherzinger force-pushed the feat/noid/conversation-list-background-sync branch from 6e11ff4 to 298813b Compare October 6, 2026 19:15
@AndyScherzinger
AndyScherzinger merged commit 72ea15c into master Oct 6, 2026
18 of 19 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

3. to review Waiting for reviews AI assisted

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants