Skip to content

Commit d012cc9

Browse files
committed
fix(webapp): move the admin shortcut to Cmd+Option+A (Cmd+Esc was uncapturable)
Cmd+Esc can't be captured on Chrome/macOS: the browser doesn't deliver an Escape keydown to the page while a modifier is held, so no listener ever sees it (confirmed with a Radix dialog, which closes on any Escape keydown, not closing on Cmd+Esc). That is why the admin shortcut did nothing. Switch it to Cmd+Option+A (Ctrl+Alt+A on Windows): an admin-only, two-modifier combo that no user-facing or browser shortcut uses, and which is actually delivered because it is a letter key. It is matched on event.code (KeyA) so it still works when Option makes the key report an alternate character on macOS, which is why it is a small raw listener in GlobalShortcuts rather than the key-based shortcut hook. The account-menu badge now reads Cmd Option A.
1 parent 43d8ada commit d012cc9

2 files changed

Lines changed: 32 additions & 22 deletions

File tree

Lines changed: 24 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,11 @@
11
import { useNavigate, useSubmit } from "@remix-run/react";
2-
import { useShortcutKeys } from "~/hooks/useShortcutKeys";
2+
import { useEffect } from "react";
33
import { useOptionalUser } from "~/hooks/useUser";
44
import { adminPath } from "~/utils/pathBuilder";
55

66
/**
77
* App-wide keyboard shortcuts, mounted once at the root so they work on every page regardless of
8-
* which side menu (if any) is rendered. Add new global shortcuts here.
9-
*
10-
* Renders nothing. Reads the user from the root loader via `useOptionalUser`, so on unauthenticated
11-
* pages there is simply no admin and nothing is registered.
8+
* which side menu (if any) is rendered. Renders nothing.
129
*/
1310
export function GlobalShortcuts() {
1411
const user = useOptionalUser();
@@ -18,20 +15,33 @@ export function GlobalShortcuts() {
1815
const isImpersonating = Boolean(user?.isImpersonating);
1916
const isAdmin = Boolean(user?.admin) || isImpersonating;
2017

21-
// ⌘/Ctrl+Esc: admins jump to the admin dashboard; while impersonating the same shortcut stops
22-
// impersonating instead. `enabledOnInputElements` so it still fires while a field is focused.
23-
useShortcutKeys({
24-
shortcut: isAdmin
25-
? { modifiers: ["mod"], key: "esc", enabledOnInputElements: true }
26-
: undefined,
27-
action: () => {
18+
useEffect(() => {
19+
if (!isAdmin) return;
20+
21+
const onKeyDown = (event: KeyboardEvent) => {
22+
// Admin-only escape hatch: Cmd+Option+A (Ctrl+Alt+A on Windows) opens the admin dashboard, or
23+
// stops impersonating while impersonating. Two modifiers keep it clear of every user-facing
24+
// shortcut, and it deliberately avoids Escape (Chrome/macOS never delivers a keydown for
25+
// Escape while a modifier is held, which is why the old Cmd+Esc did nothing).
26+
//
27+
// Matched on `event.code` (the physical key) rather than `event.key`, because holding Option
28+
// on macOS makes the "A" key report an alternate character ("å"); the physical code stays
29+
// `KeyA` regardless. This is why it's a raw listener and not the `useShortcutKeys` hook, which
30+
// matches on `event.key`.
31+
if (event.code !== "KeyA" || !event.altKey || !(event.metaKey || event.ctrlKey)) {
32+
return;
33+
}
34+
event.preventDefault();
2835
if (isImpersonating) {
2936
submit(null, { action: "/resources/impersonation", method: "delete" });
3037
} else {
3138
navigate(adminPath());
3239
}
33-
},
34-
});
40+
};
41+
42+
document.addEventListener("keydown", onKeyDown);
43+
return () => document.removeEventListener("keydown", onKeyDown);
44+
}, [isAdmin, isImpersonating, navigate, submit]);
3545

3646
return null;
3747
}

apps/webapp/app/components/navigation/SideMenu.tsx

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1420,10 +1420,10 @@ function AccountMenuItems({
14201420
title={
14211421
<div className="flex w-full items-center justify-between">
14221422
<span className={IMPERSONATION_ACCENT.text}>Stop impersonating</span>
1423-
<span className="flex items-center gap-1">
1424-
<ShortcutKey shortcut={{ modifiers: ["mod"] }} variant="medium/bright" />
1425-
<ShortcutKey shortcut={{ key: "esc" }} variant="medium/bright" />
1426-
</span>
1423+
<ShortcutKey
1424+
shortcut={{ modifiers: ["mod", "alt"], key: "a" }}
1425+
variant="medium/bright"
1426+
/>
14271427
</div>
14281428
}
14291429
icon={UserCrossIcon}
@@ -1437,10 +1437,10 @@ function AccountMenuItems({
14371437
title={
14381438
<div className="flex w-full items-center justify-between">
14391439
<span>Admin dashboard</span>
1440-
<span className="flex items-center gap-1">
1441-
<ShortcutKey shortcut={{ modifiers: ["mod"] }} variant="medium/bright" />
1442-
<ShortcutKey shortcut={{ key: "esc" }} variant="medium/bright" />
1443-
</span>
1440+
<ShortcutKey
1441+
shortcut={{ modifiers: ["mod", "alt"], key: "a" }}
1442+
variant="medium/bright"
1443+
/>
14441444
</div>
14451445
}
14461446
icon={HomeIcon}

0 commit comments

Comments
 (0)