Commit 47a6285
committed
feat: customizable keyboard shortcuts with Gmail and Superhuman presets
Shortcuts were hardcoded in three separate keydown listeners, and the `?`
cheat sheet was a hand-maintained array that had already drifted — it listed
`c` for compose while the handler that owned that key lived in a different
component.
One registry now owns every global binding. The handlers, the cheat sheet and
the new settings editor all read from it, so they cannot disagree.
PRESETS. Three, all fully editable: Comms (single keys, nothing to learn),
Gmail-style (e archives, b snoozes, l labels, / searches) and
Superhuman-style (e done, h snooze, ⌘K for everything). They are modelled on
those apps rather than copied — the bindings people actually have in muscle
memory, with anything Comms-specific keeping its own key.
SEQUENCES. Bindings can be multi-chord, so `g i`, `g m`, `g u` and `g c` jump
between views the way both apps do. A half-typed sequence is swallowed rather
than passed through, and a sequence that goes nowhere retries its last key on
its own — press `g` then `j` and you still move down the list.
CUSTOMIZATION. Settings → Shortcuts records the keys you press instead of
asking you to type a binding string, which is the only way to guarantee the
stored chord is the one the matcher will later see. Conflicts are detected and
explained, including prefix collisions: binding `g` outright means `g i` can
never complete, which is invisible until you wonder why a key stopped working.
Preferences store a preset plus a diff, not a full map, so an action added in
a later version picks up its default rather than silently having no key.
Also: Enter-sends is now a setting rather than an assumption, and the hint
under the composer follows it instead of always claiming Enter sends. ⌘↵ sends
either way.
Two of the three old listeners are gone — the palette and compose dialog now
open by event. A second global keydown listener would double-fire actions and
race the sequence buffer.
38 tests covering chord normalization (shift is recorded for named keys but
not for printable ones, or `?` would have two spellings and never match),
sequence matching, conflict and prefix detection, and the override round-trip.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RuKkB7AfSNCcwvubV99pDU1 parent 99c2b88 commit 47a6285
15 files changed
Lines changed: 1279 additions & 159 deletions
File tree
- apps/web
- src
- app/(app)
- inbox
- settings/keyboard
- components
- app
- inbox
- settings
- lib
- server/actions
- test
- packages/db/src/schema
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1 | 1 | | |
2 | 2 | | |
3 | 3 | | |
4 | | - | |
5 | 4 | | |
6 | 5 | | |
7 | 6 | | |
| |||
29 | 28 | | |
30 | 29 | | |
31 | 30 | | |
32 | | - | |
33 | 31 | | |
34 | 32 | | |
35 | 33 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
10 | 10 | | |
11 | 11 | | |
12 | 12 | | |
| 13 | + | |
| 14 | + | |
13 | 15 | | |
14 | 16 | | |
| 17 | + | |
15 | 18 | | |
16 | 19 | | |
17 | 20 | | |
| |||
54 | 57 | | |
55 | 58 | | |
56 | 59 | | |
| 60 | + | |
57 | 61 | | |
58 | 62 | | |
59 | 63 | | |
| |||
79 | 83 | | |
80 | 84 | | |
81 | 85 | | |
| 86 | + | |
| 87 | + | |
| 88 | + | |
| 89 | + | |
82 | 90 | | |
83 | 91 | | |
84 | 92 | | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
36 | 36 | | |
37 | 37 | | |
38 | 38 | | |
39 | | - | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
40 | 43 | | |
41 | | - | |
42 | | - | |
43 | | - | |
44 | | - | |
45 | | - | |
46 | | - | |
47 | 44 | | |
48 | | - | |
| 45 | + | |
49 | 46 | | |
| 47 | + | |
50 | 48 | | |
51 | | - | |
52 | 49 | | |
| 50 | + | |
53 | 51 | | |
54 | 52 | | |
55 | 53 | | |
| |||
0 commit comments