Skip to content

support keyboard-first navigation#3963

Open
ComputelessComputer wants to merge 6 commits intomainfrom
keyboard-first
Open

support keyboard-first navigation#3963
ComputelessComputer wants to merge 6 commits intomainfrom
keyboard-first

Conversation

@ComputelessComputer
Copy link
Collaborator

@ComputelessComputer ComputelessComputer commented Feb 14, 2026

@netlify
Copy link

netlify bot commented Feb 14, 2026

Deploy Preview for hyprnote-storybook canceled.

Name Link
🔨 Latest commit da07a02
🔍 Latest deploy log https://app.netlify.com/projects/hyprnote-storybook/deploys/69902059d41e55000887ddbd

@netlify
Copy link

netlify bot commented Feb 14, 2026

Deploy Preview for hyprnote canceled.

Name Link
🔨 Latest commit da07a02
🔍 Latest deploy log https://app.netlify.com/projects/hyprnote/deploys/699020591284c100084bde00

Copy link
Contributor

@devin-ai-integration devin-ai-integration bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Devin Review found 1 potential issue.

View 4 additional findings in Devin Review.

Open in Devin Review


useHotkeys(
"mod+alt+left",
"meta+alt+left",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔴 Changing mod to meta breaks top-level tab navigation on Windows/Linux

The hotkey for switching between top-level tabs was changed from mod+alt+left/right to meta+alt+left/right. In react-hotkeys-hook, mod is a cross-platform alias that maps to Meta (Cmd) on macOS and Control on Windows/Linux, whereas meta always maps to the Meta key specifically (Cmd on macOS, Windows key on Windows/Linux).

Root Cause and Impact

On macOS this change has no effect (both mod and meta resolve to Cmd). However, on Windows/Linux the shortcut changes from Ctrl+Alt+Left/Right to Win+Alt+Left/Right. The Win+Alt+Arrow combination is typically intercepted by the OS for window management, making the shortcut effectively unusable.

Every other hotkey in this file (index.tsx:851, index.tsx:868, index.tsx:879, index.tsx:915, etc.) consistently uses mod for cross-platform compatibility. The new sub-tab navigation shortcuts in ai.tsx:110 and settings.tsx:103 use ctrl+alt+left/right, which would have conflicted with the old mod+alt+left/right on Windows/Linux (since mod = Ctrl there). It appears the intent was to separate the two shortcuts on all platforms, but meta is the wrong choice for Windows/Linux — the top-level shortcut should likely remain mod+alt+left/right and the sub-tab shortcut should use a different key combination entirely.

Impact: Top-level tab navigation via keyboard is completely broken on Windows/Linux.

Prompt for agents
The root problem is that on Windows/Linux, `mod` resolves to `Ctrl`, so both the top-level tab navigation (`mod+alt+left/right`) and the new sub-tab navigation (`ctrl+alt+left/right`) would use the same physical keys. Simply reverting `meta` back to `mod` at lines 934 and 945 in index.tsx would restore Windows/Linux support but re-introduce the conflict with the sub-tab shortcuts in ai.tsx and settings.tsx. You need to pick a different key combination for one of the two levels. For example, keep `mod+alt+left/right` for top-level tabs (lines 934 and 945 in index.tsx) and change the sub-tab navigation in ai.tsx (lines 110, 125) and settings.tsx (lines 103, 118) to a non-conflicting shortcut such as `ctrl+shift+left/right` or `alt+left/right`.
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Replace focus and aria-selected classes with data-selected
attribute for better visual feedback when options are selected.
This provides more consistent styling behavior across different
interaction states.
Add Ctrl+Alt+Left/Right hotkeys to navigate between tabs in AI and
Settings components. Import react-hotkeys-hook and implement tab
switching logic that cycles through available sections.

Change existing navigation hotkeys from 'mod' to 'meta' modifier in
main body component to avoid conflicts with new tab navigation
shortcuts.
Add visual highlight styling for active settings items during 
keyboard navigation. Implements background color, border radius, 
box shadow, and padding adjustments to improve accessibility 
and user experience when navigating settings with keyboard.
Implement keyboard navigation for settings panels with arrow key
movement and space/enter activation. Add visual highlighting with
smooth scrolling and click-to-select functionality. Include smart
detection to disable navigation when editing fields or when popovers
are open to prevent conflicts with existing interactions.
Add data-settings-item and data-settings-activate attributes to
settings components and integrate useSettingsNavigation hook.

The data attributes enable keyboard navigation and accessibility
features for settings panels. The hook provides programmatic
navigation between different settings items and their interactive
elements like buttons and switches.
Remove URL validation and HTTPS API key requirement from
aiProviderSchema. Change base_url from z.url() to z.string()
and remove the refine validation that enforced API keys for
HTTPS URLs to make the schema more flexible.
Copy link
Contributor

@devin-ai-integration devin-ai-integration bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Devin Review found 5 potential issues.

View 2 additional findings in Devin Review.

Open in Devin Review

Comment on lines +110 to +138
useHotkeys(
"ctrl+alt+left",
() => {
if (currentIndex > 0) {
setActiveTab(enabledMenuKeys[currentIndex - 1]);
}
},
{
preventDefault: true,
enableOnFormTags: true,
enableOnContentEditable: true,
},
[currentIndex, setActiveTab],
);

useHotkeys(
"ctrl+alt+right",
() => {
if (currentIndex >= 0 && currentIndex < enabledMenuKeys.length - 1) {
setActiveTab(enabledMenuKeys[currentIndex + 1]);
}
},
{
preventDefault: true,
enableOnFormTags: true,
enableOnContentEditable: true,
},
[currentIndex, setActiveTab],
);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚩 ctrl+alt+left/right hotkey collision between AI/Settings sub-tabs and note-input tabs

The new ctrl+alt+left/right hotkeys added here and in settings.tsx:104-131 use the same key combination as the pre-existing hotkeys in apps/desktop/src/components/main/body/sessions/note-input/index.tsx:352-394. In react-hotkeys-hook, all mounted handlers for the same key fire. If both the AI/Settings view and the note-input view are mounted simultaneously (e.g., multiple tabs rendered in the DOM), pressing ctrl+alt+left would trigger both handlers. However, based on the tab rendering pattern (conditional rendering with {activeTab === ...}), only the active tab's content component should be mounted, which likely prevents the conflict. This warrants investigation to confirm that inactive tab content is truly unmounted rather than hidden.

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Comment on lines +23 to +29
function hasOpenPopover(): boolean {
return (
document.querySelector(
'[aria-expanded="true"], [data-state="open"][role="dialog"]',
) !== null
);
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚩 hasOpenPopover uses global document query — may match unrelated elements

The hasOpenPopover() function at line 23-29 queries the entire document for [aria-expanded="true"] or [data-state="open"][role="dialog"]. This is a global check — it will match any element in the document with these attributes, not just those within the settings panel. For example, a combobox in the header, a tooltip, or a dropdown in a different part of the app could cause hasOpenPopover() to return true, blocking keyboard navigation in settings even though the popover is unrelated. Scoping the query to the scrollRef container would be more precise, though it depends on whether Radix popovers portal outside the container (they typically do, rendering to document.body). This is a tradeoff: scoping would miss portaled popovers, but global matching creates false positives.

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Comment on lines +114 to +132
useHotkeys(
"down",
(e) => {
e.preventDefault();
navigate("down");
},
{ enableOnFormTags: false },
[navigate],
);

useHotkeys(
"up",
(e) => {
e.preventDefault();
navigate("up");
},
{ enableOnFormTags: false },
[navigate],
);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚩 Arrow key hotkeys may interfere with SearchableSelect and other interactive widgets

The useSettingsNavigation hook registers global up/down arrow key handlers with enableOnFormTags: false. While this prevents interference when focused on <input>, <textarea>, or <select> elements, it does NOT prevent interference with custom interactive components like the SearchableSelect (which uses Radix Command internally and relies on arrow keys for option navigation). When the SearchableSelect popover is open, the hasOpenPopover check ([aria-expanded="true"]) should catch it and bail out of the navigate function. However, the useHotkeys handler still calls e.preventDefault() at useSettingsNavigation.ts:117 before calling navigate(), which internally checks hasOpenPopover. This means the arrow key default behavior may be prevented even when a popover is open, potentially breaking keyboard navigation within popovers.

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant