Settle account ties and stop discarding caches on every launch

Two devices editing an account in the same instant — or migrating one,
since every migrated account is stamped at the epoch — each kept its own
label and considered the matter closed. Equal timestamps are now broken
by the label, on the incoming side and in the conflict resolver alike,
the same rule the context already uses.

Cached feeds were thrown away on every ordinary launch: recomputing
statuses posts a notification, and the observer ran before the value it
compares against had been seeded. The seed now happens first.

Cache ownership is checked once at launch and compared against a
recorded owner afterwards. An unresolved identity at launch means the
owner cannot be established at all — a switch to an unseen Apple account
while offline looks exactly like that — so the caches are discarded;
mid-session it is a transient, and with iCloud Drive off it happens on
every activation, so it no longer clears anything.

Whether a Keychain item syncs is fixed when it is written, so a login
stored while sync was off stayed on this device while its account record
travelled, leaving every other device with an account whose credential
never arrives. Turning sync on rewrites them.

Reseeding a deleted zone marks the account records dirty even while the
category is off, so re-enabling it restores them instead of finding an
empty outbox.
This commit is contained in:
Arkadiusz Fal
2026-08-26 23:53:29 +02:00
parent d9244cf0d7
commit b37ca40d37
11 changed files with 158 additions and 43 deletions

View File

@@ -71,4 +71,16 @@ struct Account: Identifiable, Codable, Hashable, Sendable {
/// Whether this account was produced by the legacy migration rather than an
/// explicit sign-in.
var isMigrated: Bool { id == instanceID }
/// Whether `other` should replace this copy.
///
/// Ordered by the app-managed `updatedAt`, with the label settling an exact
/// tie. The tie is reachable — two devices can migrate the same account in
/// the same instant, and every migrated account is stamped at the epoch — and
/// without a tie-break each device would keep its own label and consider the
/// matter closed, which is a divergence neither would ever notice.
func isOutranked(by other: Account) -> Bool {
if other.updatedAt != updatedAt { return other.updatedAt > updatedAt }
return (other.displayName ?? "") > (displayName ?? "")
}
}