Let activation know the accounts before it repairs the selection

Foreground activation started identity resolution, the deletion repair and
the CloudKit fetch as three things racing each other. With a tombstone for
the selected account already on disk and a sibling login on the same server
whose status had not been read back yet, the repair ran against `checking`
— which it will not repair onto — and landed on local. It commits, so the
status refresh arriving a moment later changed nothing: the selection no
longer named an account for a second repair to act on.

The steps now run as one sequence in both platform paths, because each one
is what makes the next correct: the identity says whose accounts these are,
the status refresh says which of them this device can use, the repair reads
those statuses, and the fetch goes last so a deletion it delivers repairs
against the same refreshed picture.
This commit is contained in:
Arkadiusz Fal
2026-09-05 22:27:21 +02:00
parent f44c1d91c3
commit ca73b68ad9
2 changed files with 68 additions and 34 deletions

View File

@@ -795,6 +795,35 @@ struct DataContextRepairTests {
#expect(!manager.pendingContextUpload)
}
@Test("A repair made while a sibling was still being checked never comes back")
func repairIsNotReconsideredWhenStatusesSettle() {
let instances = InstancesManager()
let instance = makeInstance("inv")
instances.add(instance)
defer { instances.remove(instance) }
let manager = makeManager(instances: instances)
let selected = manager.addAccount(instanceID: instance.id)
let sibling = manager.addAccount(instanceID: instance.id)
// Nothing is known about the sibling yet, so it cannot be repaired
// onto: `checking` is the absence of an answer, not a usable login.
manager.setStatus(.checking, for: sibling.id)
manager.setContext(.account(selected.id))
manager.removeAccount(selected.id)
#expect(manager.currentContext == .local)
// Statuses settling afterwards is not a second chance. The selection no
// longer names an account, so the tombstone repair has nothing left to
// act on and local is where the user stays — which is why foreground
// activation refreshes statuses *before* it lets a tombstone move
// anything, rather than repairing twice.
manager.setStatus(.signedIn, for: sibling.id)
#expect(!manager.correctContextIfAccountDeleted())
#expect(manager.currentContext == .local)
}
@Test("A signed-in account on another server is never the repair")
func repairNeverCrossesInstances() {
let instances = InstancesManager()