@@ -246,6 +246,24 @@ const NOOP_GATE_HOOKS: GateLifecycleHooks = {
246246 onGateClosed : ( ) => { } ,
247247}
248248
249+ /**
250+ * Wrap a gate's `resolve` so `onGateClosed` fires exactly once no matter
251+ * which of accept / cancel / auto-deny settles it first.
252+ */
253+ function onceClosed < T > (
254+ onGateClosed : ( ) => void ,
255+ resolve : ( value : T ) => void ,
256+ ) : ( value : T ) => void {
257+ let closed = false
258+ return ( value ) => {
259+ if ( ! closed ) {
260+ closed = true
261+ onGateClosed ( )
262+ }
263+ resolve ( value )
264+ }
265+ }
266+
249267/**
250268 * Subscribe the permission/operator gate events to the shell's overlays.
251269 * Returns a dispose function that removes exactly the listeners this call added.
@@ -276,14 +294,7 @@ export function wireGates(
276294
277295 function onPermission ( ev : PermissionGateEvent ) : void {
278296 hooks . onGateOpened ( )
279- let closed = false
280- const resolve : PermissionGateEvent [ "resolve" ] = ( outcome ) => {
281- if ( ! closed ) {
282- closed = true
283- hooks . onGateClosed ( )
284- }
285- ev . resolve ( outcome )
286- }
297+ const resolve = onceClosed ( hooks . onGateClosed , ev . resolve )
287298 const choices = permissionChoicesFromRequest ( ev . request )
288299 const collapsedBody = permissionBodyFromRequest ( ev . request , { hint : true } )
289300 // Nothing was collapsed → no expand affordance, so the overlay leaves the
@@ -388,14 +399,7 @@ export function wireGates(
388399
389400 function onOperator ( ev : OperatorGateEvent ) : void {
390401 hooks . onGateOpened ( )
391- let closed = false
392- const resolve : OperatorGateEvent [ "resolve" ] = ( result ) => {
393- if ( ! closed ) {
394- closed = true
395- hooks . onGateClosed ( )
396- }
397- ev . resolve ( result )
398- }
402+ const resolve = onceClosed ( hooks . onGateClosed , ev . resolve )
399403 const choices = operatorChoicesFromOptions ( ev . options )
400404 // Guarded the same way as the permission gate: correctness must not rest
401405 // on callers of closeInsetOverlay remembering to null the cancel hook
0 commit comments