one modifier, and arrows beside the vim keys

Standardised on `ctrl+shift` for all three global bindings, and added arrows everywhere
the vim keys work — `h`/`←`, `l`/`→`, `j`/`↓`, `k`/`↑` are now interchangeable.

  ctrl+shift+→  or  ctrl+shift+l      next theme
  ctrl+shift+←  or  ctrl+shift+h      previous theme
  ctrl+shift+space                    the list

── Why ctrl+shift and not the alternatives ──

Checked GNOME's actual defaults rather than reasoning from convention:

  <Control><Alt>Left/Right         switch workspace
  <Control><Shift><Alt>Left/Right  move window to workspace
  <Super><Alt>Left/Right           switch workspace

So ctrl+alt is unusable — those arrows change your workspace, and `ctrl+alt+l` locks the
screen, which is presumably why the original bindings put the letters on ctrl+shift in
the first place. `shift+alt` is free on Linux but is the keyboard-layout switcher on
Windows and `alt+space` opens the window menu.

`ctrl+shift` has no OS binding at all. Its one collision is select-by-word inside a text
field — which is now explicitly protected.

── The guard moved earlier ──

Typing was only checked once the list was OPEN. With arrows on the global cycle that was
no longer enough: `ctrl+shift+←/→` is select-by-word in every text field, and stealing it
mid-sentence to change a colour scheme would be indefensible. The check now runs before
the global cycle too.

Verified against a focused textarea: theme unchanged, and the word selection still
happened. The first run of that test reported a hijack, and the test was wrong — it
clicked a selector that matched nothing, swallowed the failure, and read body-focus as
a text field.

`ctrl+alt+space` still opens the list. It shipped an hour ago and fingers learn faster
than they unlearn.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-17 01:30:33 +00:00
co-authored by Claude Opus 5
parent 4ecd45c4a9
commit f492905023
2 changed files with 40 additions and 23 deletions
@@ -44,7 +44,7 @@ export const ThemeSwitcher = () => {
<DropdownMenu>
<DropdownMenuTrigger asChild>
<button
title={`Theme: ${theme.name} · ctrl+alt+space for the list, ctrl+shift+h/l to cycle`}
title={`Theme: ${theme.name} · ctrl+shift+space for the list, ctrl+shift+←/→ to cycle`}
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+alt+space · ctrl+shift+h/l
ctrl+shift+space · ctrl+shift+←/→
</DropdownMenuLabel>
<DropdownMenuSeparator />
{Object.entries(families).map(([family, entries]) => (
@@ -110,7 +110,7 @@ export const ThemeSwitcher = () => {
})}
</ul>
<p className="mt-6 text-center text-xs opacity-60">
j/k move · l preview · h reset · enter keep · esc cancel
j/k or ↑/↓ move · l or → preview · h or ← reset · enter keep · esc cancel
</p>
</div>
</div>,