[PM-40478] fix: Update Send tab visibility immediately after policy sync - #2917
[PM-40478] fix: Update Send tab visibility immediately after policy sync#2917matt-livefront wants to merge 1 commit into
Conversation
🤖 Bitwarden Claude Code ReviewOverall Assessment: APPROVE Reviewed the new Code Review Details
One additional low-severity note, mentioned here rather than inline: |
| /// Streams sync-complete events, re-checking the Send policy and updating tab visibility once | ||
| /// a sync has fully persisted (organizations and policies both included). | ||
| private func streamSyncComplete() { | ||
| syncCompleteStreamTask = Task { [policyService, syncService] in | ||
| for await _ in syncService.syncCompletePublisher() { | ||
| let isSendDisabled = await policyService.policyAppliesToUser(.disableSend) | ||
| await MainActor.run { [weak self] in | ||
| self?.updateTabs(isSendEnabled: !isSendDisabled) | ||
| } | ||
| } | ||
| } | ||
| } |
There was a problem hiding this comment.
🎨 SUGGESTED: streamOrganizations() still runs its own disableSend check, which can race this one.
Details and rationale
streamOrganizations() (lines 241-244) computes isSendDisabled from the organizations emission, which — as the PR objective describes — fires before replacePolicies/replacePoliciesNew have run, so it reads the pre-update policy value and calls updateTabs(isSendEnabled:) with a stale result.
Both stream tasks independently await policyService.policyAppliesToUser(.disableSend) and then hop to the main actor, so the delivery order of the two updateTabs calls isn't guaranteed. If the organizations task's hop lands after the sync-complete task's, the stale value wins and the tab bar stays wrong until the next sync — the same symptom this PR fixes.
Since fetchSync is the only writer of policy data (replacePolicies and replacePoliciesNew have no other callers), the new sync-complete stream already covers every policy change. Dropping the policy check from streamOrganizations() would leave that stream driving only the vault title (as described in the PR objective) and remove the possibility of a stale write:
let canShowVaultFilter = await vaultRepository.canShowVaultFilter()
if organizations.isEmpty || !canShowVaultFilter {
navigator.rootViewController?.title = Localizations.myVault
} else {
navigator.rootViewController?.title = Localizations.vaults
}
// policy check + updateTabs removed; handled by streamSyncComplete()
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #2917 +/- ##
==========================================
- Coverage 81.10% 81.09% -0.01%
==========================================
Files 1038 1038
Lines 67332 67356 +24
==========================================
+ Hits 54608 54623 +15
- Misses 12724 12733 +9 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
🎟️ Tracking
PM-40478
📔 Objective
disableSendorganization policy) previously took two syncs to update after the policy was toggled server-side.TabCoordinatorrecomputed visibility only whenvaultRepository.organizationsPublisher()emitted, butSyncService.fetchSync()persists organizations before policies, so that emission always raced ahead of the policy data it was meant to reflect.SyncService.syncCompletePublisher(), a new multicast completion signal fired at the true end of a successful sync (alongside the existingonFetchSyncSucceededdelegate call), modeled onNotificationCenterService.didEnterBackgroundPublisher().TabCoordinatornow also subscribes to this publisher and rechecks thedisableSendpolicy once a sync has fully settled, in addition to its existing organizations-driven subscription (which continues to drive the vault title unchanged).📸 Screenshots
send-tab-before.mov
send-tab-after.mov