Skip to content

Defer overlay idle until slash command dispatch settles - #773

Merged
TheGreatAxios merged 9 commits into
mainfrom
cl-6711-defer-queued-gates-until-slash-or-palette-command-dispatch
Sep 4, 2026
Merged

TheGreatAxios merged 9 commits into
mainfrom
cl-6711-defer-queued-gates-until-slash-or-palette-command-dispatch

Conversation

@TheGreatAxios

Copy link
Copy Markdown
Collaborator

Summary

  • Slash and palette Enter keep the overlay host until the selected command has claimed it, so a queued permission gate cannot open in that gap
  • A live gate stays on screen; the command surface waits with a system line instead of vanishing
  • Async settings holds the host across the permissions list fetch

Verification

  • bun run typecheck, bun run build, and bun run test pass
  • Reproducing tests in src/tui/slash-popup-gate.test.ts fail on the first commit and pass on HEAD

Fixes CL-6711

@linear-code

linear-code Bot commented Sep 3, 2026

Copy link
Copy Markdown

CL-6711

@TheGreatAxios TheGreatAxios left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 TheGreatAxios left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 TheGreatAxios left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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().then does not abort on dismiss (src/tui/command-surfaces.ts), so a reservation can withhold queued-gate drain after Esc
  • openOrQueue in src/tui/gate-wire.ts still keys only on overlayList, so a new gate during the /settings list() gap can take the host
  • Comments in src/tui/shell.ts still describe a busy openListOverlay as a silent no-op

@TheGreatAxios TheGreatAxios left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 TheGreatAxios left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 TheGreatAxios left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 TheGreatAxios left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 TheGreatAxios left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.
@TheGreatAxios
TheGreatAxios force-pushed the cl-6711-defer-queued-gates-until-slash-or-palette-command-dispatch branch from a2a1e6d to 2e7d285 Compare September 4, 2026 05:11
@TheGreatAxios
TheGreatAxios merged commit afce776 into main Sep 4, 2026
5 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.

1 participant