Skip to content

Fix dead Home "Delete meeting" confirmation#1126

Merged
r3dbars merged 1 commit into
r3dbars:mainfrom
acorretti:fix/home-delete-confirmation-menu-loop
Jun 15, 2026
Merged

Fix dead Home "Delete meeting" confirmation#1126
r3dbars merged 1 commit into
r3dbars:mainfrom
acorretti:fix/home-delete-confirmation-menu-loop

Conversation

@acorretti

@acorretti acorretti commented Jun 14, 2026

Copy link
Copy Markdown
Contributor

Why

The Home recent-meeting ⋯ menu's Delete meeting action did nothing: no confirmation dialog, no deletion. Fixes #1125.

Two stacked defects on the Home row path:

  1. ClosureMenuItem.invoke() ran its handler synchronously, inside NSMenu.popUp's modal tracking loop. SwiftUI won't present an .alert(item:) / .sheet(item:) from that nested loop, so the Delete handler set homeDeleteConfirmation but nothing showed. Fix Home dashboard row actions, hover reveal, and stats focus ring #1110 made the menu handler fire at all (it previously no-op'd on a deallocated weak target), which uncovered this layer.

  2. Three legacy .alert(item:) modifiers (homeDeleteConfirmation, homeDeleteFailure, pendingAudioRetentionWindow) were stacked on the settings root view. SwiftUI keeps only the last stacked legacy alert, so the delete confirmation (first) and the delete-failure alert were both shadowed. Only the audio-retention alert (last) could ever present.

The tell while debugging: Report issue (a .sheet) started working once the defer landed, but Delete (an .alert) still didn't, because stacked sheets don't shadow each other and stacked legacy alerts do.

Product Impact

  • Affects: meetings
  • Lane: meeting reliability
  • Why this matters: deleting a recent meeting from Home was impossible through the menu, and the delete-failure alert (plus any non-last stacked alert) was silently shadowed too.

What changed

  • ClosureMenuItem.invoke() now defers its handler to the next main-runloop turn via DispatchQueue.main.async { [handler] in handler() }, off the NSMenu.popUp tracking loop. The handler stays owned by the item (the capture keeps it alive), so the deallocation trap Fix Home dashboard row actions, hover reveal, and stats focus ring #1110 fixed does not come back.
  • The three Home alert states route through a single .alert(item: rootAlertBinding) instead of three stacked .alert(item:). A small private RootAlert enum plus activeRootAlert / rootAlertBinding pick the active state and clear it on dismiss. All seven existing assignment sites are unchanged.
  • Source-contract guards in UIAutomationSurfaceContractTests.swift: the menu handler must defer, the three alerts must share one presenter, and no .alert(item: $homeDelete…) / $pendingAudioRetentionWindow may be re-stacked.

How I checked it

  • scripts/dev/agent-preflight.sh
  • Selected checks from .agents/test-matrix.yml for the files changed (Sources/UI/** + root Tests/*.swift map to build + fast tests)
  • bash build.sh --no-open
  • bash run-tests.sh (5353/5353)
  • Performance budget passed (build.sh --no-open bundle gate: "Performance budget OK")
  • bash run-integration-smoke.sh (N/A, no Sources/Meeting/ or Sources/TranscriptedCore/ changes)
  • swift test (N/A, no Package.swift / core seam changes)
  • Manual check: Settings → Home → ⋯ → Delete meeting shows the confirmation; confirm deletes, cancel dismisses cleanly. Report issue sheet and Show in Finder still work. The failed-meeting delete and the Storage audio-retention prompt (the other two states now sharing the presenter) still confirm.

Risk Review

  • Privacy / local-first behavior reviewed (no off-device payload or analytics shape changes)
  • Storage path or migration impact reviewed (delete still resolves the real on-disk URL through the existing HomeMeetingDeletion path; no migration)
  • Public-facing copy stays concrete and matches current product scope (delete-confirmation copy unchanged, still HomeDeleteConfirmationPolicy)
  • Release/update impact reviewed (none; no version, appcast, or updater flow touched)
  • UI changes include sanitized .agent-review/visuals/ evidence (not attached; this makes an existing dialog present with unchanged copy, and the live Home list shows private meeting titles. Happy to add a cropped capture of just the confirmation dialog if wanted.)
  • No private transcripts, audio, tokens, personal paths, or customer data are included

Notes

The recent-meeting overflow (⋯) menu's Delete action set
homeDeleteConfirmation but no confirmation dialog ever appeared. Two
stacked defects on the Home row path:

1. ClosureMenuItem invoked its handler synchronously, inside NSMenu.popUp's
   modal tracking loop. SwiftUI does not present an alert/sheet from that
   nested loop, so the state was set but nothing showed. PR r3dbars#1110 made the
   handler fire at all (it previously no-op'd on a deallocated weak target),
   which exposed this next layer. Defer the handler to the next main-runloop
   turn, keeping it owned by the item so it cannot deallocate first.

2. Three legacy .alert(item:) modifiers (homeDeleteConfirmation,
   homeDeleteFailure, pendingAudioRetentionWindow) were stacked on the
   settings root view. SwiftUI keeps only the last, so the delete
   confirmation (first) and the delete-failure alert were shadowed. Route all
   three through one .alert(item: rootAlertBinding); every call site is
   unchanged.

"Report issue" opens a .sheet and needed only fix 1, since stacked sheets do
not shadow each other. Add source-contract guards for both fixes.
@r3dbars

r3dbars commented Jun 15, 2026

Copy link
Copy Markdown
Owner

Thanks Alfonso — this is the right area and the CI/fast suite is green, but I'm holding merge for one issue from codex-review.

The new shared alert binding clears all three alert states when SwiftUI writes nil. If a confirmation action fails synchronously during dismissal, for example dictation delete throws or failed-meeting delete returns false, the follow-up homeDeleteFailure can be cleared before it presents.

Could you adjust the dismissal path to clear only the dismissed alert, or defer the failure alert until after dismissal?

Proof run here: codex-review --mode branch against origin/main; inside that, bash run-tests.sh passed 5353/5353.

@r3dbars r3dbars merged commit 64558a3 into r3dbars:main Jun 15, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Home "Delete meeting" confirmation never appears

2 participants