You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
With a reply, an edit, or a comment open, pressing Escape in the media gallery also clears that mode.
On 14ab7f9c1. Paths are under apps/webapp/src/components/chatroom/.
The gallery handles Escape in its own window keydown listener (hooks/useGalleryKeyboard.ts:56-66, :130). When the image is zoomed, Escape resets the zoom. Otherwise it closes the gallery.
The window Escape handler also runs on window for the same key (components/MessageComposer/hooks/useHandleEscKey.ts:69-74). It checks the link dialog, the emoji overlays, and the mention picker, but not the gallery (:30-44). So it clears reply, edit, or comment, and calls clearContent(true) for edit and comment (:46-54).
The gallery calls stopPropagation() only when zoomed (hooks/useGalleryKeyboard.ts:59-61). That cannot stop another listener on window anyway.
Both listeners run for every Escape while the gallery is open, except when the gallery overflow menu is open. That menu listens on window in the capture phase and stops propagation, so neither listener runs (components/ChatMediaGallery/GalleryOverflowMenu.tsx:207-213). Their order depends on when each effect last registered, so either one can run first. In the steps below, the window Escape handler runs first, because choosing Edit re-registers it before the gallery opens.
§Overlay contention in the chatroom agent docs gives the Escape order as link dialog, emoji, mention, then reply and edit. It does not name the gallery (apps/webapp/src/components/chatroom/CLAUDE.md:79).
Steps to reproduce
Open a channel. Choose Edit on one of your messages.
Click an image in the feed, so the gallery opens.
Press Escape.
See the gallery close and edit mode end.
Acceptance criteria
With the gallery open, Escape resets the zoom or closes the gallery. Reply, edit, and comment stay as they were, and so does the composer text. This holds whichever listener runs first.
With the gallery closed, Escape in the composer still clears reply, edit, or comment, as today.
With the gallery overflow menu open, Escape closes only the menu. The gallery stays open, and reply, edit, and comment stay as they were.
apps/webapp/src/components/chatroom/CLAUDE.md §Overlay contention says that the media gallery owns Escape while it is open. It also names the mechanism that keeps the window Escape handler from clearing reply, edit, or comment while the gallery is open.
Agent Brief
Category: bug Summary: The window Escape handler must ignore an Escape that the media gallery handles.
Current behavior:
The gallery and the window Escape handler both listen for Escape on window. The window Escape handler does not know the gallery is open, so one Escape both closes the gallery and clears reply, edit, or comment.
Desired behavior:
An Escape pressed while the gallery is open never clears reply, edit, or comment, whichever listener runs first. The gallery handles that Escape alone.
Key interfaces:
useHandleEscKey() — the window Escape handler. It closes the link dialog or an emoji overlay itself, then returns. It returns early while the mention picker is visible.
useGalleryKeyboard() — the gallery's window keydown listener.
The gallery store's open state in useChatMediaGalleryStore.
The window Escape handler must not rely on the gallery's open state at the moment it runs. Listener order is not fixed. When the gallery's listener runs first, it closes the gallery in the same event. A later read then says closed, and the mode is still cleared. One option is to record the gallery's open state in a capture-phase listener, before either listener runs. The same shape is one option for the mention picker in #268.
Do not skip on defaultPrevented. ProseMirror sets it on every Escape in the editor, so Escape in the composer would stop clearing. Moving the gallery's own listener to the capture phase on window breaks the overflow menu. Both listeners are then on window in the capture phase. The menu's stopPropagation() does not stop a listener on the same target and phase. So one Escape closes both, in either order.
The evidence is a code trace on 14ab7f9c1, checked by two reviewers. Two review harnesses modeled the listener order in a DOM emulator. They copy the listener logic and do not load the real hooks. It was not reproduced in a browser.
Problem
With a reply, an edit, or a comment open, pressing Escape in the media gallery also clears that mode.
On
14ab7f9c1. Paths are underapps/webapp/src/components/chatroom/.windowkeydown listener (hooks/useGalleryKeyboard.ts:56-66,:130). When the image is zoomed, Escape resets the zoom. Otherwise it closes the gallery.windowfor the same key (components/MessageComposer/hooks/useHandleEscKey.ts:69-74). It checks the link dialog, the emoji overlays, and the mention picker, but not the gallery (:30-44). So it clears reply, edit, or comment, and callsclearContent(true)for edit and comment (:46-54).stopPropagation()only when zoomed (hooks/useGalleryKeyboard.ts:59-61). That cannot stop another listener onwindowanyway.Both listeners run for every Escape while the gallery is open, except when the gallery overflow menu is open. That menu listens on
windowin the capture phase and stops propagation, so neither listener runs (components/ChatMediaGallery/GalleryOverflowMenu.tsx:207-213). Their order depends on when each effect last registered, so either one can run first. In the steps below, the window Escape handler runs first, because choosing Edit re-registers it before the gallery opens.§Overlay contention in the chatroom agent docs gives the Escape order as link dialog, emoji, mention, then reply and edit. It does not name the gallery (
apps/webapp/src/components/chatroom/CLAUDE.md:79).Steps to reproduce
Acceptance criteria
apps/webapp/src/components/chatroom/CLAUDE.md§Overlay contention says that the media gallery owns Escape while it is open. It also names the mechanism that keeps the window Escape handler from clearing reply, edit, or comment while the gallery is open.Agent Brief
Category: bug
Summary: The window Escape handler must ignore an Escape that the media gallery handles.
Current behavior:
The gallery and the window Escape handler both listen for Escape on
window. The window Escape handler does not know the gallery is open, so one Escape both closes the gallery and clears reply, edit, or comment.Desired behavior:
An Escape pressed while the gallery is open never clears reply, edit, or comment, whichever listener runs first. The gallery handles that Escape alone.
Key interfaces:
useHandleEscKey()— the window Escape handler. It closes the link dialog or an emoji overlay itself, then returns. It returns early while the mention picker is visible.useGalleryKeyboard()— the gallery's window keydown listener.useChatMediaGalleryStore.Out of scope
Notes
The window Escape handler must not rely on the gallery's open state at the moment it runs. Listener order is not fixed. When the gallery's listener runs first, it closes the gallery in the same event. A later read then says closed, and the mode is still cleared. One option is to record the gallery's open state in a capture-phase listener, before either listener runs. The same shape is one option for the mention picker in #268.
Do not skip on
defaultPrevented. ProseMirror sets it on every Escape in the editor, so Escape in the composer would stop clearing. Moving the gallery's own listener to the capture phase onwindowbreaks the overflow menu. Both listeners are then onwindowin the capture phase. The menu'sstopPropagation()does not stop a listener on the same target and phase. So one Escape closes both, in either order.The evidence is a code trace on
14ab7f9c1, checked by two reviewers. Two review harnesses modeled the listener order in a DOM emulator. They copy the listener logic and do not load the real hooks. It was not reproduced in a browser.