feat(hint-mode): add Vim-style keyboard navigation - #202
Merged
Conversation
- guard against IME composition eating CJK input - detect BlockNote/ProseMirror editors via closest(), not activeElement - drop wholesale key-swallowing — let DevTools/reload/menu accelerators through - guard focus() with document.contains() after click - aria-hidden=true on overlay for screen readers - reset hintModeActiveRef on provider unmount - filter out excess labels past 676-element ceiling instead of returning duplicate ZZ
…abels - Flatten HintIndicator JSX: collapse inner wrapper div into outer, keep same visual result - Swap local isEditorFocused helper for shared isInputFocused from use-keyboard-shortcuts - Rewrite label-assigner final pass as explicit loop; drop redundant filter
Covers 10 behaviors end-to-end against the built Electron bundle: - Alt+F activation renders overlay portal, HINT indicator, and badges - Escape deactivates and clears overlay + indicator - Typing a prefix dims non-matching badges; indicator echoes typed chars via kbd - Backspace removes last typed char and restores full hint set opacity - Typing a full unique label fires click and deactivates (tracked via window.__hintClicks) - Non-matching chars are silently ignored - F alone activates when no editor is focused - F inside a focused textarea types normally (hint mode does not activate) - Alt+F overrides editor focus (textarea does not receive f due to preventDefault) - Disabled and off-screen elements are excluded by the DOM scanner Tests inject controlled DOM so they don't depend on shifting sidebar labels, and match badges to targets by screen rect so they survive label-assignment algorithm changes. Opacity assertions use expect.poll to handle the 100ms CSS transition deterministically.
h4yfans
added a commit
that referenced
this pull request
May 6, 2026
h4yfans
added a commit
that referenced
this pull request
May 6, 2026
feat(hint-mode): add Vim-style keyboard navigation
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Add Vim/Vimium-style hint mode: press
Alt+F(orFwhen no editor is focused) to overlay typed labels on every clickable element, then type a label to activate the target mouselessly.Why
Users who prefer keyboard-driven workflows needed a way to reach any button, link, tab, or sidebar item without the mouse. Existing keyboard shortcuts covered only the most-used actions; this closes the gap for the long tail.
How
lib/dom-scanner.ts) — queries clickable roles (button, link, tab, treeitem, menuitem, option, tabindex,data-hint), filters by enabled + visible + in-viewport + outside the hint overlay itselflib/label-assigner.ts) — mnemonic-first: unique first letters get single-char labels (Nfor Notes); collisions try second-letter candidates (TA/TS); remaining get sequential two-char codes, with prefix-collision prevention so no single-char label is a prefix of any two-char label — the auto-click-on-unique-match UX depends on this guaranteecontexts/hint-mode/context.tsx) — exposesactivate/deactivate/typeChar/backspace; module-levelhintModeActiveReflets other keyboard hooks suspend themselves when hint mode is activecomponents/hint-overlay/) —HintOverlayrenders intodocument.bodyatzIndex: 2147483646; badges dim non-matching hints to 0.3 opacity as the user narrows;HintIndicatorshows the status chip + typed charshooks/use-hint-activation.ts) — captures keydown at the window level;Alt+Falways activates; bareFactivates only when no editor is focused so it doesn't steal typing in notes/journals;Escapedeactivates;Backspaceundoes a charType
feat— new featureTest plan
HintModeProvider) covered via Vitest (see earlier commits on branch)HintModeProviderintegration test (use-hint-activation.test.tsx)tests/e2e/hint-mode.e2e.tscovers 10 behaviors against the built Electron bundle: activation shortcuts, overlay/indicator rendering, prefix narrowing with opacity dimming, backspace undo, unique-full-match auto-click, non-matching ignored, editor-focus suppression of bareF,Alt+Foverride inside editors, disabled/off-screen exclusionAlt+F→ type label → target clicks; tried macOS, checkede.code === 'KeyF'works even when Alt producesƒChecklist