feat(desktop): keyboard shortcuts for everything, in the menu bar, and ChatGPT-style tooltips
What
Shortcuts. One table (apps/desktop/src/shared/shortcuts.ts) names every window command and its chord. Three things read it:
- The menu bar (
main/app-menu.ts). The app menu gains Settings… ⌘,. File gets New Plan, Copy Plan, Reveal in Finder, and Delete Plan…. View gets the sidebar and panel toggles, Toggle Full Screen and Exit Full Screen, Board/Code/Transcript, and Previous/Next Plan. Help gets Keyboard Shortcuts. A menu click sends the command to the focused window over a newonMenuCommandsubscription. - One renderer keymap (
renderer/app-commands.ts). The control a command belongs to offers it while it stands: the sidebar toggle offers ⌘B, the panel toggle offers ⌥⌘B and ⌥⌘1–3, the toolbar offers Copy and Delete. A chord therefore answers exactly where its control does; when nothing offers it, the key passes through. Chromium hands a Command chord to the page before the menu, so a press runs once. - The Keyboard shortcuts page and the settings search. The page lists every shortcut in groups (Plans, Window, Settings), and each one can be found by name in the search.
New shortcuts:
| Shortcut | Command |
|---|---|
| ⌘N | New plan |
| ⇧⌘C | Copy plan |
| ⌥⌘R | Reveal in Finder (VS Code/Cursor's chord) |
| ⌘⌫ | Delete plan; still asks, and a text field keeps its own ⌘⌫ |
| ⌥⌘↑ / ⌥⌘↓ | Previous/next plan; New plan counts as the head of the list |
| ⌥⌘1/2/3 | Board/Code/Transcript |
| ⇧⌘↩ | Panel full screen on/off (the pane-maximize chord in iTerm and Warp) |
| Esc | Exit full screen |
| ⌘/ | Keyboard shortcuts |
| ⌘[ | Back out of Settings |
⌘B, ⌥⌘B, ⌘, and ⌘F moved from three ad hoc listeners onto the same keymap.
Escape is in the table so View and the shortcuts page can show it, but the key itself stays with app.tsx's layered Escape (mute first, then one layer at a time, full screen included). So the keymap skips it, and the menu ignores an Escape that reaches it through the keyboard (triggeredByAccelerator); only a click on Exit Full Screen runs that item. Every chord holds ⌘, so none can clash with the user's talk or stop key, which take the whole machine.
Tooltips (renderer/tooltip.tsx). One Tooltip replaces the native title on the window's icon buttons: sidebar toggle, ⋯, the panel's own row from #1841 (tabs, Expand panel/Exit full screen, Hide panel), the toolbar's Show panel, account button, microphone/stop, start plan, clear search, the shortcut row buttons, reset, and System Settings….
- It is a dark pill, light on dark, with no arrow. It shows the label, then the chord in muted glyphs.
- It waits out a new generated motion token,
--hint-delay(500ms). The design contract forbids literal ms in CSS, hence the token. - Once one has shown, a neighbour shows at once.
- A press, any key, a scroll, the pointer leaving, or the window blurring hides it. Keyboard focus shows it.
- It sits below the control, flips above where the window ends, and is clamped to the window edges.
- Fade and scale use the motion tokens, so reduced motion and capture runs drop the animation.
- It sets
aria-keyshortcuts, and addsaria-describedbyonly when the label is not already the control's name. - Its words are text nodes, so session replay masks them with everything else (
maskTextSelector: "*"); nothing rides an attribute.
Inline hints. New plan and Back to plans show their chord muted at the row's right end on hover or keyboard focus, taking no layout room. The settings search field prints a persistent muted ⌘F that disappears on focus or typing, per Dean's follow-up.
Notes
- Luke's guide: the renderer AGENTS.md asks to teach the guide about new features. The hand-written facts half was deleted in 03152a93 (LUKE-206), and nothing sends a guide to a model any more, so there was nothing to teach. AGENTS.md is left alone, per its own rule.
- Menu items are not greyed out when their command can't run; clicking one just does nothing. Greying them would need the renderer's state mirrored into main, and that didn't seem worth it.
Verification
./scripts/check.shexits 0.- New tests drive the public components:
- each shortcut triggers its action, and is left alone where its control is absent;
- a text field keeps its own ⌘⌫;
- menu commands run as their chords do;
- the tooltip shows on hover and on keyboard focus, warms its neighbours, hides on press, key, Escape, and scroll, and flips above at the bottom of the window;
- ⌘F lands in the search, and the field's hint gives way to a query.
- I rendered the built renderer in headless Chrome with a stubbed bridge to check the pills, the hover hints, the ⌘F field, and the shortcuts page. Note that Linux needs DejaVu to draw ⌘/⌥.
CI could not verify the Mac UI, and ./scripts/verify.sh was not run (this is a Linux VM). LUKE-159 is the manual Mac pass. Electron's menu bar and how it routes key equivalents are only checkable on a Mac.
🤖 Generated with Claude Code