fix: the page menu must not claim a modified key (ChatGPT Codex, PR #123)

The menu's early return sat ABOVE `app.js`'s `metaKey` guard, so with the menu
open `Cmd-R` reached `menuKey`, was answered `ignore`, and was then defaulted
away by `if (!e.ctrlKey && !e.altKey) e.preventDefault()` — true for Meta.
Reload was blocked. `Ctrl-j`/`Alt-Home` had the mirror fault and MOVED the menu,
two lines below a comment saying Ctrl and Alt stay the browser's.

Ordering plus one bail: the `metaKey` guard goes back above the menu branch,
`pageMenuKeydown` returns on `ctrlKey || altKey`, and what remains claims
unconditionally — every unmodified key, `ignore` included, which is the rule the
menu exists to keep.

The harness assertion installs its `defaultPrevented` probe AFTER load so it
runs second and sees what the page decided, and ends on an unmodified `j`
asserting `["j", true]` — without which "nothing was prevented" is satisfied by
a page that claims nothing. Proved by restoring the reviewed code: it fails on
`Control+j` moving the active row.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FC2jVn8Au7E9JPkw6bwXXH
This commit is contained in:
Edward Langley
2026-09-21 23:36:43 -07:00
parent 0a920f1d54
commit 9156b48f12
3 changed files with 78 additions and 7 deletions
+11 -7
View File
@@ -724,6 +724,9 @@ el.grid.addEventListener("mousedown", (event) => {
/** The menu's own keys, decided in `shell.js`. Every one of them is claimed:
* an unhandled key must not reach the grid behind the menu. */
function pageMenuKeydown(e) {
// Ctrl and Alt combinations are the browser's, exactly as below, so the menu
// neither acts on one nor claims it — `Ctrl-j` is not `j` (improvise-rw6l).
if (e.ctrlKey || e.altKey) return;
const items = pageAxes[pageMenu.axis].items;
const action = menuKey(e.key, { count: items.length, active: pageMenu.active });
if (action.kind === "close") closePageMenu();
@@ -732,23 +735,24 @@ function pageMenuKeydown(e) {
pageMenu.active = action.item;
paintPageMenu();
}
// Ctrl and Alt combinations stay the browser's, exactly as below.
if (!e.ctrlKey && !e.altKey) e.preventDefault();
// Every unmodified key is CLAIMED, `ignore` included: the grid is behind
// the menu and must not also act on it.
e.preventDefault();
}
window.addEventListener("keydown", (e) => {
// The inline editor owns the keyboard while it is open. It stops
// propagation, so this is belt and braces for a focus we did not predict.
if (editing) return;
// …and so does the page menu, which is the third state of that kind: it is
// anchored to the grid, so letting `hjkl` through would scroll it away.
// BrowserKey drops anything held with the platform meta key precisely so
// Cmd-R does not arrive as a bare `r`; do not claim it here either.
if (e.metaKey) return;
// …and the page menu owns the keyboard too, which is the third state of
// that kind: `hjkl` reaching the grid would scroll it out from under it.
if (pageMenu) {
pageMenuKeydown(e);
return;
}
// BrowserKey drops anything held with the platform meta key precisely so
// Cmd-R does not arrive as a bare `r`; do not claim it here either.
if (e.metaKey) return;
// The notice's <summary> is a real control and owns Enter and Space while
// it has focus. Claiming them here would call `preventDefault` on the
// browser's own disclosure toggle, so a keyboard visitor could focus it and