Defer overlay idle until slash command dispatch settles - #773
TheGreatAxios merged 9 commits into
Conversation
TheGreatAxios
left a comment
There was a problem hiding this comment.
Neckbeard
A busy non-palette openListOverlay now defers instead of no-op (src/tui/shell.ts). Palette stacked over an already-open help/settings/mcp, then Enter on that same command, can restore the prior overlay and queue a second open so Esc is needed twice. Tests only stack over a gate.
Comments at the in-place filter path still say busy open is a no-op. src/tui/landing.test.ts swaps "queue" for "follow-up" in a chrome-absence assertion that is unrelated to slash dispatch.
TheGreatAxios
left a comment
There was a problem hiding this comment.
Greybeard
Idle-notify still means idle: notifyOverlayClosed does not flush a deferred surface.
closeReplaceableOverlay, deferBusyCommandOpen, and reserveOverlayHost are the right jobs, not one helper in three clothes. Host occupancy is still split across skip-notify, reservation count, and the deferred slot, and wireGates.openOrQueue does not see reservations.
Not blocking this PR's idle-notify contract. Own host-busy in one predicate before adding another occupancy switch.
TheGreatAxios
left a comment
There was a problem hiding this comment.
Critic
Slash and palette Enter keep the overlay host until the selected command claims it, so a queued permission gate cannot drain into that gap.
No blocking defects. CL-6711 outcomes hold, and notifyOverlayClosed does not flush deferred surfaces. Cannot GitHub-approve this PR (same author).
Should-fix, not blocking:
- Settings and permissions
list().thendoes not abort on dismiss (src/tui/command-surfaces.ts), so a reservation can withhold queued-gate drain after Esc openOrQueueinsrc/tui/gate-wire.tsstill keys only onoverlayList, so a new gate during the/settingslist() gap can take the host- Comments in
src/tui/shell.tsstill describe a busyopenListOverlayas a silent no-op
TheGreatAxios
left a comment
There was a problem hiding this comment.
Critic
Approve CL-6711 occupancy at 1dbcd08. Slash/palette accept reserves the host until dispatch settles; non-gate lists replace; gates defer command surfaces; onDispose vs onCancel is correct; notifyOverlayClosed does not flush deferred surfaces. No blocking defects.
Should-fix (not blocking): closeInsetOverlay does not bump overlayGeneration, so Esc while settings is still showing during a cycle-triggered permissions.list() can resurrect the menu when list() resolves. Esc during a reservation-only list() also leaves overlayHostReservations until list() finally releases.
TheGreatAxios
left a comment
There was a problem hiding this comment.
Greybeard
Architecture holds at 1dbcd08. Idle-notify is idle-only (isOverlayHostIdle). Deferred flush is not in notifyOverlayClosed. Occupancy is one predicate. onDispose vs onCancel is the right split (cleanup on replace; Esc-only navigation skipped by closeReplaceableOverlay). No blocking architecture defects.
TheGreatAxios
left a comment
There was a problem hiding this comment.
Neckbeard
No blocking hygiene. Remaining real-shaped holes (already should-fix on critic): close-then-open without a reservation can idle-notify a queued gate; Esc with overlayList set does not bump generation. Nits: Bun.sleep(60) around idle-notify, as unknown as PluginsSurfaceDeps, notifyIfHostIdle after a successful deferred flush is dead, TUI.md names notifyOverlayClosed which is not exported.
TheGreatAxios
left a comment
There was a problem hiding this comment.
Critic (3e1d81c)
Genuinely fine. Idle-notify still means idle and does not flush deferred surfaces. Painted Esc notifies once via closeInsetOverlay. No blocking or should-fix defects.
Prior should-fixes at 72602ba remain landed. Occupancy architecture holds (greybeard). Hygiene nits from 1dbcd08 that were real-shaped are closed here (dead notifyIfHostIdle wrapper, Esc double-notify, dispose abort, occupancy comments, isOverlayHostIdle assertions).
File-for-later: reservation-only Esc draining a queued gate on the next tick; Bun.sleep(60) for renderer key delivery.
TheGreatAxios
left a comment
There was a problem hiding this comment.
Critic (fc3e81a)
Genuinely fine. The new occupancy tests pin stacked-palette same-kind /help replace, add-provider deferral over a live gate, and reservation Esc abort (with the stdin ESC sequence timeout). resolved after closeInsetOverlay is intended deny-on-dismiss.
No blocking or should-fix defects.
File-for-later: Esc-during-reservation with a queued gate (same Esc must not dismiss the drain); stale release() epoch; deny shape { allow: false }.
Enter closed the command list before the selected command could claim the host, so a queued permission gate opened in the gap and the command vanished. Idle-notify now waits until dispatch settles, and a live gate is not closed to make room for the command surface.
Queued gates were still keying only on overlayList, so a permission event during /settings list() or a busy non-command open could steal the single host. Occupancy is now one idle predicate: live list, deferred command slot, and host reservations. Command surfaces opt into deferral; decision gates are an explicit isGate flag.
A queued gate could still steal the host during settings-to-plugins accept, same-kind /help deferred instead of replacing, and Esc during /settings list() still painted settings. Occupancy is now one idle predicate. closeReplaceableOverlay skips dismiss onCancel so /help does not bounce through add-provider; MCP cleanup is onDispose.
A close-then-open gap let a queued gate steal the host, and Esc during an in-flight settings list could resurrect the menu after the operator had already dismissed it.
Esc with a visible list was notifying twice (abort microtask plus close), and occupancy comments still described overlayList instead of idle.
a2a1e6d to
2e7d285
Compare
Summary
Verification
bun run typecheck,bun run build, andbun run testpasssrc/tui/slash-popup-gate.test.tsfail on the first commit and pass on HEADFixes CL-6711