egui delivers a frame's input events for keys pressed against the frame
before it. A modal that opens and consumes keys in the same frame can
therefore act on a keypress aimed at whatever was on screen previously.
The shape
A handler opens a modal and, in that same frame, calls the shared key
consumption helper. The events it receives were produced while the previous
frame was on screen, when the modal did not exist. A key the user pressed at
the terminal, or at a different dialog, lands on the confirm button of a
dialog they have not seen yet.
The same hazard appears in a second form: a modal whose content changes
when an async result lands can consume a key pressed against the frame that
showed the old content.
Where it stands
The delete dialog's keyboard path is fixed: it handles the confirm before
adopting a landed dirty count, so a keyboard confirm always executes the
force value that was painted.
The rest of the pattern is not.
show_create_dialog and show_close_session_dialog consume confirm keys
in the frame they open.
- The delete dialog's mouse Delete button is still evaluated after the
dirty count is adopted, so a pointer release landing on the exact frame
the probe resolves runs against a warning that was never painted. One
frame wide, and unlike a held key it does not repeat, so it is far
narrower than the keyboard case was.
Why it matters
Most of these confirm something recoverable. The delete path is the one
where a stray Enter discards uncommitted work, and that specific case is
closed. What remains is a general correctness gap: no modal in the app can
currently promise that a confirm executed what the user was looking at.
What done looks like
One guard, applied where modals are opened rather than repeated per dialog:
a modal ignores input events belonging to the frame before it appeared, and
a modal whose content changed this frame ignores input belonging to the
frame that showed the old content.
Doing this generically is worth more than fixing the two named dialogs,
because the next modal added inherits the fix instead of the hazard.
egui delivers a frame's input events for keys pressed against the frame
before it. A modal that opens and consumes keys in the same frame can
therefore act on a keypress aimed at whatever was on screen previously.
The shape
A handler opens a modal and, in that same frame, calls the shared key
consumption helper. The events it receives were produced while the previous
frame was on screen, when the modal did not exist. A key the user pressed at
the terminal, or at a different dialog, lands on the confirm button of a
dialog they have not seen yet.
The same hazard appears in a second form: a modal whose content changes
when an async result lands can consume a key pressed against the frame that
showed the old content.
Where it stands
The delete dialog's keyboard path is fixed: it handles the confirm before
adopting a landed dirty count, so a keyboard confirm always executes the
forcevalue that was painted.The rest of the pattern is not.
show_create_dialogandshow_close_session_dialogconsume confirm keysin the frame they open.
dirty count is adopted, so a pointer release landing on the exact frame
the probe resolves runs against a warning that was never painted. One
frame wide, and unlike a held key it does not repeat, so it is far
narrower than the keyboard case was.
Why it matters
Most of these confirm something recoverable. The delete path is the one
where a stray Enter discards uncommitted work, and that specific case is
closed. What remains is a general correctness gap: no modal in the app can
currently promise that a confirm executed what the user was looking at.
What done looks like
One guard, applied where modals are opened rather than repeated per dialog:
a modal ignores input events belonging to the frame before it appeared, and
a modal whose content changed this frame ignores input belonging to the
frame that showed the old content.
Doing this generically is worth more than fixing the two named dialogs,
because the next modal added inherits the fix instead of the hazard.