arrows are the only global binding

`ctrl+shift+h`/`l` are gone. The arrows do that job, and two spellings for one action is
what invited the reasonable guess that `ctrl+shift+j` moved down — which opens the
browser console.

Worth recording that the reported conflict was not one we had: nothing bound
`ctrl+shift+j`. The global letters were `h` and `l` only, and the `j`/`k` keys live
inside the list with NO modifier. But an interface that makes someone guess a wrong key
is the interface's fault, not the guess's.

Inside the list the vim keys stay, unmodified: `j`/`k`/`l`/`h` beside `↓`/`↑`/`→`/`←`.
Nothing there can collide with a browser shortcut, and this is a vim person's
application.

Verified: `ctrl+shift+l` no longer cycles, `ctrl+shift+→` does, and `j` then `l` still
browses inside the list.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-17 01:32:05 +00:00
co-authored by Claude Opus 5
parent f492905023
commit 54f63b70df
2 changed files with 14 additions and 9 deletions
@@ -44,7 +44,7 @@ export const ThemeSwitcher = () => {
<DropdownMenu>
<DropdownMenuTrigger asChild>
<button
title={`Theme: ${theme.name} · ctrl+shift+space for the list, ctrl+shift+←/→ to cycle`}
title={`Theme: ${theme.name} · ctrl+shift+←/→ to cycle · ctrl+shift+space for the list`}
aria-label="Theme"
className="flex h-8 w-8 items-center justify-center rounded-full transition-transform hover:scale-110 cursor-pointer"
style={{ color: 'var(--header-fg)' }}
@@ -54,7 +54,7 @@ export const ThemeSwitcher = () => {
</DropdownMenuTrigger>
<DropdownMenuContent align="end" className="z-50 max-h-[70vh] w-56 overflow-y-auto">
<DropdownMenuLabel className="text-xs font-normal text-muted-foreground">
ctrl+shift+space · ctrl+shift+←/→
ctrl+shift+←/→ · ctrl+shift+space
</DropdownMenuLabel>
<DropdownMenuSeparator />
{Object.entries(families).map(([family, entries]) => (
+12 -7
View File
@@ -7,16 +7,21 @@ import type { ThemeId } from './themes';
*
* Two modes, and the second is the good one:
*
* ctrl+shift+l / ctrl+shift+→ next theme, from anywhere, no UI
* ctrl+shift+h / ctrl+shift+← previous theme
* ctrl+shift+space open the list, then browse it
* ctrl+shift+→ next theme, from anywhere, no UI
* ctrl+shift+← previous theme
* ctrl+shift+space open the list, then browse it
*
* One modifier for all three, which took some choosing. `ctrl+alt+arrows` switches WORKSPACE on GNOME and
* `ctrl+alt+l` locks the screen, so ctrl+alt is unusable for this. `shift+alt` is free on Linux but is the
* keyboard-layout switcher on Windows. `ctrl+shift` has no OS binding at all; its only collision is
* word-selection inside a text field, which the guard below already prevents.
*
* Vim keys and arrows are interchangeable everywhere: `h`/`←`, `l`/`→`, `j`/`↓`, `k`/`↑`.
* The GLOBAL bindings are arrows only. `ctrl+shift+h`/`l` used to work too, and two spellings for one
* action invited the guess that `ctrl+shift+j` moved down — which opens the browser console instead. One
* spelling, no guessing.
*
* Inside the list the vim keys stay, UNMODIFIED — `j`/`k`/`l`/`h` alongside the arrows. No modifier means
* no collision with anything the browser owns, and this is a vim person's application.
*
* ── Why browsing applies the theme ──
*
@@ -78,14 +83,14 @@ export function useThemeKeys() {
const target = event.target as HTMLElement | null;
const typing = Boolean(target && (target.isContentEditable || /^(input|textarea|select)$/i.test(target.tagName)));
// Cycle from anywhere, list open or not.
// Cycle from anywhere, list open or not. Arrows only — see the note above.
if (event.ctrlKey && event.shiftKey && !typing) {
if (event.key === 'L' || event.key === 'l' || event.key === 'ArrowRight') {
if (event.key === 'ArrowRight') {
event.preventDefault();
setTheme(step(current, 1));
return;
}
if (event.key === 'H' || event.key === 'h' || event.key === 'ArrowLeft') {
if (event.key === 'ArrowLeft') {
event.preventDefault();
setTheme(step(current, -1));
return;