Skip to content

Commit 874a9a6

Browse files
committed
Extract the once-guarded gate-close wrapper in wireGates
onPermission and onOperator each rewrapped resolve with the same closed-once bookkeeping; onceClosed makes that a single helper both gate handlers share.
1 parent 797b86c commit 874a9a6

1 file changed

Lines changed: 20 additions & 16 deletions

File tree

src/tui-opentui/gate-wire.ts

Lines changed: 20 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)