Skip to content

fix(ios): background widget write is issue-aware and keeps honest metrics; completion write persists the settled tier (#76, #77)#78

Merged
psimaker merged 1 commit into
mainfrom
fix/issue-76-77-widget-background-status
Jul 7, 2026
Merged

fix(ios): background widget write is issue-aware and keeps honest metrics; completion write persists the settled tier (#76, #77)#78
psimaker merged 1 commit into
mainfrom
fix/issue-76-77-widget-background-status

Conversation

@psimaker

@psimaker psimaker commented Jul 7, 2026

Copy link
Copy Markdown
Owner

Ships in the not-yet-archived 1.8.0 (build 33 unchanged, never uploaded).

#76: BackgroundSyncService.completeSync persisted "idle"/"error" directly,
bypassing the issue-derived tier from #73 - a successful background run
(scheduled refresh, overnight catch-up, relay wake-up) overwrote an honest
attention snapshot (parked share, disconnected required peer, collision)
with a green idle until the next foreground open. The static background
context cannot recompute unresolvedIssues (the bridge is already stopped
on the background-owned path; peer grace state lives only in the manager),
so the foreground now persists an issue floor into the app group on every
widget write: max severity of unresolvedIssues plus unreachable folders,
excluding the kinds a background run itself invalidates (.staleSync - a
successful sync resolves staleness by definition; .backgroundSync - the
completion write knows the fresher outcome), so background-only operation
cannot stick a false amber. completeSync maps its result to the same
severities backgroundSyncIssueItem surfaces in-app, folds the floor in,
and derives the tier through SyncHeaderModel.deriveWidgetStatus - one
cascade for every status surface (decisions 012/013). An unknown persisted
floor decodes as warning, never none. One deliberate visible change: a
failed background sync now shows amber "Needs Attention" (the tier the
header shows for it on next open) instead of red "Sync Error". Wire
compatibility unchanged: fromWire still maps anything unknown to
attention, so no combination decodes into a false green.

#76 follow-up: the same write also read the folder count and synced-file
events only after cleanupBackgroundManaged had stopped the bridge - which
then reports an empty folder list, so every background-owned completion
rendered "Vaults: 0" on the widget and lost the files count - and stamped
its own timestamp as "last sync" for failed runs too. The completion write
now runs before cleanup (the values are read while the bridge is still
up), a stopped bridge falls back to the previous snapshot's folder count
instead of zero, and only a successful run stamps the last-sync triple -
a failure carries the previous one forward, so "last synced" keeps naming
the last real sync. Assembly is a pure function (fallback matrix fully
unit-tested).

#77: updateWidgetSyncMetrics derived the completion tier while
activeWidgetSyncStart was still non-nil, so completeWidgetSyncSession
persisted a stale .syncing snapshot that the poll-end write immediately
replaced - two snapshot writes and widget reloads per sync completion.
The session is now finalized before the tier is derived; the poll-end
write finds an identical snapshot and dedupes to a no-op.

No strings added, no Go changes - the earlier-1.8.0 xcframework rebuild
note still applies before archiving.

Verified: full Xcode test plan green (iPhone 17 Pro simulator, 257/257
incl. the 20 new tests); the two #77 tests were demonstrated red on the
unfixed code first, the #76 defects are traced in the Go bridge (stopped
engine reports "[]" for folders and events).

Fixes #76
Fixes #77

…rics; completion write persists the settled tier (#76, #77)

Ships in the not-yet-archived 1.8.0 (build 33 unchanged, never uploaded).

#76: BackgroundSyncService.completeSync persisted "idle"/"error" directly,
bypassing the issue-derived tier from #73 - a successful background run
(scheduled refresh, overnight catch-up, relay wake-up) overwrote an honest
attention snapshot (parked share, disconnected required peer, collision)
with a green idle until the next foreground open. The static background
context cannot recompute unresolvedIssues (the bridge is already stopped
on the background-owned path; peer grace state lives only in the manager),
so the foreground now persists an issue floor into the app group on every
widget write: max severity of unresolvedIssues plus unreachable folders,
excluding the kinds a background run itself invalidates (.staleSync - a
successful sync resolves staleness by definition; .backgroundSync - the
completion write knows the fresher outcome), so background-only operation
cannot stick a false amber. completeSync maps its result to the same
severities backgroundSyncIssueItem surfaces in-app, folds the floor in,
and derives the tier through SyncHeaderModel.deriveWidgetStatus - one
cascade for every status surface (decisions 012/013). An unknown persisted
floor decodes as warning, never none. One deliberate visible change: a
failed background sync now shows amber "Needs Attention" (the tier the
header shows for it on next open) instead of red "Sync Error". Wire
compatibility unchanged: fromWire still maps anything unknown to
attention, so no combination decodes into a false green.

#76 follow-up: the same write also read the folder count and synced-file
events only after cleanupBackgroundManaged had stopped the bridge - which
then reports an empty folder list, so every background-owned completion
rendered "Vaults: 0" on the widget and lost the files count - and stamped
its own timestamp as "last sync" for failed runs too. The completion write
now runs before cleanup (the values are read while the bridge is still
up), a stopped bridge falls back to the previous snapshot's folder count
instead of zero, and only a successful run stamps the last-sync triple -
a failure carries the previous one forward, so "last synced" keeps naming
the last real sync. Assembly is a pure function (fallback matrix fully
unit-tested).

#77: updateWidgetSyncMetrics derived the completion tier while
activeWidgetSyncStart was still non-nil, so completeWidgetSyncSession
persisted a stale .syncing snapshot that the poll-end write immediately
replaced - two snapshot writes and widget reloads per sync completion.
The session is now finalized before the tier is derived; the poll-end
write finds an identical snapshot and dedupes to a no-op.

No strings added, no Go changes - the earlier-1.8.0 xcframework rebuild
note still applies before archiving.

Verified: full Xcode test plan green (iPhone 17 Pro simulator, 257/257
incl. the 20 new tests); the two #77 tests were demonstrated red on the
unfixed code first, the #76 defects are traced in the Go bridge (stopped
engine reports "[]" for folders and events).

Fixes #76
Fixes #77
@coderabbitai

coderabbitai Bot commented Jul 7, 2026

Copy link
Copy Markdown

Warning

Review limit reached

@psimaker, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 3 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 4608ee17-2c48-498b-96eb-8fe316ceb31d

📥 Commits

Reviewing files that changed from the base of the PR and between 7290afc and dce8c76.

📒 Files selected for processing (6)
  • CHANGELOG.md
  • docs/decisions/013-background-widget-write-issue-floor.md
  • ios/VaultSync/Services/BackgroundSyncService.swift
  • ios/VaultSync/Services/SyncthingManager.swift
  • ios/VaultSyncTests/BackgroundWidgetStatusTests.swift
  • ios/VaultSyncTests/WidgetCompletionWriteTests.swift
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/issue-76-77-widget-background-status

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.

@psimaker psimaker merged commit a0c26f4 into main Jul 7, 2026
13 checks passed
@psimaker psimaker deleted the fix/issue-76-77-widget-background-status branch July 7, 2026 21:23
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

1 participant