feat(sync): show next scheduled sync time in Sync Settings - #290
TimeToBuildBob wants to merge 4 commits into
Conversation
Surfaces "when will it sync next?" next to the existing "last sync" status, derived from SyncScheduler's SYNC_INTERVAL_MS and the last completed run — no new persisted state. Part of ActivityWatch#274 (the per-peer-names half is tracked separately, blocked on aw-server-rust JNI support). Git-Session-Id: 4f6d
|
| if (!enabled) return "Next sync: sync is disabled" | ||
| if (lastStatus == null) { | ||
| return "Next sync: shortly (first sync runs about a minute after ActivityWatch starts)" | ||
| } |
There was a problem hiding this comment.
Displayed schedule can diverge
After the service is recreated or sync is re-enabled, the scheduler sets the first run for one minute later. This code instead uses the previous completion time plus 15 minutes, so it can show an old timestamp or “due now” even though the actual next run is still a minute away. Alarm-triggered worker completions can also update the completion time without re-anchoring the scheduler. The status should come from the scheduler's actual next trigger rather than treating the last completion as authoritative.
Knowledge Base Used: Sync scheduling and execution
There was a problem hiding this comment.
The two concrete problems this comment described are fixed:
- "uses the previous completion time plus 15 minutes ... old timestamp or 'due now' even though the actual next run is still a minute away" — fixed in fb7a853:
SyncScheduler.start()now writes the realfirstRunAt(now+60s) intoschedulerNextRunAt, andSyncSettingsActivityreads that instead of computinglastCompletedAt + SYNC_INTERVAL_MS. - "Alarm-triggered worker completions can also update the completion time without re-anchoring the scheduler" — fixed in 8ed0c97:
SyncWorker.doWork()now re-anchorsschedulerNextRunAton its own completion too, so a restart-via-alarm no longer leaves the display stuck.
What's left is a residual, pre-existing characteristic of the architecture rather than something this PR introduces: SyncScheduler keeps two independent triggers — the in-process Handler chain and an AlarmManager.setInexactRepeating fallback (both present on master before this PR) — and Android's own OS-level batching on the inexact alarm means its actual fire time isn't perfectly deterministic either. schedulerNextRunAt reflects the last-known scheduling intent from whichever mechanism completed most recently, which is the best available signal without restructuring the underlying dual-scheduler design (out of scope for this additive UI feature). The display is intentionally framed as a best-effort estimate for the settings screen, not a hard scheduling guarantee.
| private fun updateNextSyncStatus() { | ||
| tvNextSyncStatus.text = formatNextSyncStatus( | ||
| prefs.isSyncEnabled(), | ||
| prefs.getLastSyncStatus(), | ||
| combinedDateTimeFormat(), | ||
| ) |
There was a problem hiding this comment.
Next-sync status becomes stale
This value is refreshed only when the activity refreshes, the switch changes, or a completed-sync broadcast arrives. If the screen remains visible when the displayed time passes—especially while a sync is running, delayed, or cancelled—the old timestamp remains instead of changing to “due now” until another event triggers a refresh. A refresh at the displayed deadline would keep the status accurate.
Knowledge Base Used: Sync scheduling and execution
Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!
There was a problem hiding this comment.
Fixed in ff8b060 — added a 30s self-rescheduling Handler tick (started in onStart, stopped in onStop) that re-renders tvNextSyncStatus while the screen is visible, so a passed deadline flips to "due now" without waiting for a switch toggle or a completed-sync broadcast. Mirrors the existing Handler/postDelayed pattern already used by SyncScheduler.
🤖 AI code reviewSafe to merge — no P0/P1 findings on latest reviewUpdated after inline dispositions on finding threads — this is the current state; the verdict below is frozen at review time and is kept as the historical record of that pass. Finding disposition
This PR adds a 'Next sync' status line to the Sync Settings screen. It introduces a new persisted preference Needs a look — P2 onlyConfidence 4/5 3 findings ·
|
| commit | score | findings | engine | when |
|---|---|---|---|---|
0d868961e5b4 |
3/5 | 2 | llm | 2026-09-17 23:22 UTC |
fb7a8539ffb2 |
4/5 | 2 | llm | 2026-09-18 01:41 UTC |
Reviewed 8ed0c978ab5d · openrouter/deepseek/deepseek-v4-flash-0731 · llm engine · 193s · about this reviewer
Maintainer commands
@TimeToBuildBob review (own line) — fresh review · @TimeToBuildBob fix — a worker acts on the findings. Once per comment; 👀 = received.
formatNextSyncStatus was computing lastCompletedAt + SYNC_INTERVAL_MS, which
diverges from the actual scheduled time whenever the scheduler is (re)started:
start() delays the first run by 60s (not 15min), so the displayed time could
show an old timestamp or "due now" while the real next run is still a minute away.
Fix: SyncScheduler now writes its actual scheduled next-run epoch-ms into
AWPreferences ("schedulerNextRunAt") on start() and after each completed sync.
SyncSettingsActivity reads this stored value and passes it to formatNextSyncStatus,
which uses it in preference to the computed lastCompletedAt+interval fallback.
The fallback is kept for the first-install case before any scheduler run is
recorded.
Adds a unit test that verifies the scheduler-anchored time is used over the
computed interval (shows 01:31 from the registered +60s, not 01:45 from +15min).
Git-Session-Id: ae8f1145-f19e-5b27-8216-f6ff4db022ef
|
Fixed the P1 (displayed schedule can diverge):
Fix: The P2 (staleness while screen stays visible) is a UI refresh-interval concern — the status is updated on |
|
@greptileai review |
…etion
SyncWorker (the AlarmManager fallback path) never wrote schedulerNextRunAt,
so after the app process is killed and restarted via the alarm instead of
SyncScheduler.start(), the 'Next sync' display got stuck showing a stale
past timestamp ('due now') forever, even though syncs kept happening on
the alarm's interval.
Git-Session-Id: e32bc91c-e9ed-5c1a-b261-c6e94c6ac45d
|
@greptileai review |
While SyncSettingsActivity is visible, the 'Next sync' text was only refreshed on a switch toggle or a completed-sync broadcast. If the displayed deadline passed while the screen stayed open, the text kept showing the old timestamp instead of flipping to 'due now' until another event arrived. Add a 30s self-rescheduling Handler tick (started in onStart, stopped in onStop) that re-renders the next-sync text, mirroring the existing Handler pattern already used by SyncScheduler. Git-Session-Id: c450adb7-6205-57e9-9a95-3a7d9ecb50ba
|
@greptileai review |
Summary
SyncScheduler.SYNC_INTERVAL_MS+ the last completed sync time — no new persisted state, pure UI surfacing on top of the existing scheduler intervalPart of #274. The per-peer-names half of that issue is tracked separately (blocked on aw-server-rust JNI support).
Test plan
formatNextSyncStatusunit tests added (disabled / never-synced / normal / elapsed cases) —mobile/src/test/.../SyncSettingsActivityTest.kt./gradlew :mobile:testStandardDebugUnitTest— full unit suite green (22/22 inSyncSettingsActivityTest, no regressions elsewhere)