Skip to content

Conversation

@ComputelessComputer
Copy link
Collaborator

• Improved settings layout design
• General updates and refinements

@coderabbitai
Copy link

coderabbitai bot commented Oct 21, 2025

📝 Walkthrough

Walkthrough

This PR refactors desktop UI components and consolidates keyboard shortcuts. Changes include redesigning the settings general panel with inline language and vocabulary inputs, reorganizing the settings layout navigation, centralizing hotkey handlers for tabs, updating search focus management via context, integrating settings shortcuts, improving editor keydown handling with global shortcut whitelisting, and enhancing various UI components with keyboard badges and progress indicators.

Changes

Cohort / File(s) Summary
Settings General & Shared Components
apps/desktop/src/components/settings/general.tsx, apps/desktop/src/components/settings/shared.tsx
Replaces popover language picker with inline searchable panel; converts vocabulary textarea to token-based UI with chip removals; introduces mock permission states with toggle actions; adds SettingRow component extraction for reusable setting rows with title, description, and Switch control.
Settings Layout Navigation
apps/desktop/src/routes/app/settings/_layout.tsx
Reworks left navigation from fixed columns to aside with three Group components; replaces button styling with ghost Button variants from @hypr/ui; updates header styling with reduced height and neutral background; adjusts active/hover states for tab items.
Search Context & Components
apps/desktop/src/contexts/search/ui.tsx, apps/desktop/src/components/main/body/search.tsx
Replaces isFocused/onFocus/onBlur state with inputRef-based approach; wires global mod+k hotkey to focus input via useHotkeys; removes hotkey-based search behavior from search component; moves focus management to local inline handlers.
Notification Settings
apps/desktop/src/components/settings/notification.tsx
Replaces local SettingRow with imported shared SettingRow; removes p-6 padding on outer container; simplifies heading styling.
Tab & Window Shortcuts Consolidation
apps/desktop/src/components/main/body/index.tsx
Moves hotkey logic from inline components to centralized useTabsShortcuts and useNewTab hooks; consolidates keyboard shortcuts: mod+n (close then open), mod+t (open), mod+w (close or close app), mod+1..9 (select tabs).
Shell Context & Settings Integration
apps/desktop/src/contexts/shell/index.tsx, apps/desktop/src/contexts/shell/settings.ts
Adds useSettings hook with global mod+, hotkey to trigger settings window via @hypr/plugin-windows API; extends ShellContextType with settings field; integrates openSettings into ShellContext provider.
Editor Keyboard Handling
packages/tiptap/src/editor/index.tsx
Replaces inline keydown stub with handleKeyDown implementation featuring global shortcut whitelist ["w", "n", "t", ",", "j", "l", "k"]; adds Backspace and Tab handling; removes separate useEffect-based keydown listener.
Sidebar & Navigation Enhancements
apps/desktop/src/components/main/sidebar/profile/index.tsx, apps/desktop/src/components/main/sidebar/profile/ota/index.tsx
Adds keyboard badge (KbdGroup with ⌘ and ,) to Settings menu item; enhances OTA UpdateChecker downloading state with inline circular progress indicator reflecting downloadProgress.percentage.
Provider Nesting Reordering
apps/desktop/src/routes/app/main/_layout.tsx
Reorders provider hierarchy: moves ShellProvider inside SearchUIProvider while keeping it within SearchEngineProvider; no JSX content changes, only wrapper order adjustment.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant App
    participant ShellContext as Shell<br/>Context
    participant SearchContext as Search<br/>Context
    participant TipTap as TipTap<br/>Editor
    participant Window as Window/Tab<br/>Manager

    Note over User,Window: Global Keyboard Shortcuts Flow

    User->>App: Press mod+k
    App->>SearchContext: useHotkeys triggers
    SearchContext->>SearchContext: inputRef.current?.focus()

    User->>App: Press mod+,
    App->>ShellContext: useSettings hotkey fires
    ShellContext->>Window: windows.show('settings')
    Window->>App: Settings window opens

    User->>App: Press mod+t (in editor)
    App->>TipTap: handleKeyDown checks whitelist
    TipTap->>App: Bypasses editor, allows global
    App->>Window: Shortcut routes to tab manager

    User->>App: Press mod+n (in editor)
    App->>TipTap: handleKeyDown matches whitelist
    TipTap->>Window: Lets global handler route
    Window->>App: Close current + open new tab

    Note over User,Window: Consolidated Control Flow
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

The PR spans 10+ files with mixed architectural and UI changes, but follows consistent refactoring patterns. Keyboard shortcut consolidation is well-contained across predictable locations. Context provider reordering and search focus migration are straightforward. Public API changes (SearchUIContextValue, useSettings export) are minimal and backward-compatible in intent. The variety of changes demands separate reasoning per cohort but individual changes are not densely complex.

Possibly related PRs

  • added summary language configuration #1328: Modifies settings general language UI with language picker and vocabulary changes closely aligned with the language/vocabulary refactoring in this PR.
  • Vocab Fix  #1448: Adds persistence and mutation logic for the jargon/vocabulary feature that this PR refactors into token-based UI.
  • v1: New headers + React19 #1540: Broadens search context and ref handling that relates to the inputRef and search focus management changes in this PR.

Pre-merge checks and finishing touches

❌ Failed checks (1 warning, 1 inconclusive)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
Title Check ❓ Inconclusive The title "General settings improvements" is vague and overly broad relative to the actual scope of changes. While it does reference real modifications to the general settings component (general.tsx with UI improvements to language picker, vocabulary, and permissions), the changeset extends far beyond general settings improvements. The PR includes significant changes to keyboard shortcuts (mod+k, mod+t, mod+w, mod+n, mod+,), search UI refactoring, shell context integration, main body layout restructuring, and various UI refinements across multiple systems. The term "improvements" lacks specificity and doesn't convey what was actually changed, potentially confusing teammates reviewing the history.
✅ Passed checks (1 passed)
Check name Status Explanation
Description Check ✅ Passed The description mentions "Improved settings layout design" and "General updates and refinements," which are directly related to the changeset. These points accurately reflect the layout restructuring in the settings routes and UI refinements throughout multiple components. While the description is brief and somewhat general, it covers real aspects of the changes and is not off-topic or misleading.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch jj-branch-24

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 5

🧹 Nitpick comments (9)
apps/desktop/src/routes/app/settings/_layout.tsx (1)

98-110: Add consistent icon–label spacing and current-state hook on nav items

Improves readability and a11y signaling.

           <Button
             key={tab}
             variant="ghost"
             className={cn([
-              "shadow-none w-full justify-start hover:bg-neutral-200",
+              "shadow-none w-full justify-start gap-2 hover:bg-neutral-200",
               activeTab === tab && "bg-neutral-200",
             ])}
+            aria-current={activeTab === tab ? "page" : undefined}
             onClick={() => navigate({ search: { tab } })}
           >
apps/desktop/src/components/settings/general.tsx (8)

232-236: Use cn([...]) array style and group classes per project guideline

Aligns with apps/desktop cn usage and aids readability.

-                className={cn(
-                  "flex flex-wrap items-center w-full px-2 py-1.5 gap-1.5 rounded-lg bg-white border border-neutral-200 focus-within:border-neutral-300 min-h-[38px]",
-                  languageInputFocused && "border-neutral-300",
-                )}
+                className={cn([
+                  // layout
+                  "flex flex-wrap items-center w-full min-h-[38px] px-2 py-1.5 gap-1.5",
+                  // visuals
+                  "rounded-lg bg-white border border-neutral-200 focus-within:border-neutral-300",
+                  // state
+                  languageInputFocused && "border-neutral-300",
+                ])}

As per coding guidelines


295-298: cn([...]) array style inside options for consistency

Keep cn usage consistent here too.

-                          className={cn(
-                            "flex items-center justify-between px-3 py-2 text-sm text-left transition-colors w-full",
-                            languageSelectedIndex === index ? "bg-neutral-200" : "hover:bg-neutral-100",
-                          )}
+                          className={cn([
+                            "flex items-center justify-between px-3 py-2 text-sm text-left transition-colors w-full",
+                            languageSelectedIndex === index ? "bg-neutral-200" : "hover:bg-neutral-100",
+                          ])}

As per coding guidelines


245-257: Add aria-label to remove-language button

Improves screen reader clarity.

                     <Button
                       type="button"
                       variant="ghost"
                       size="sm"
                       className="h-3 w-3 p-0 hover:bg-transparent ml-0.5"
+                      aria-label={`Remove ${lang}`}
                       onClick={(e) => {
                         e.stopPropagation();
                         const updated = (value.spoken_languages ?? []).filter(l => l !== lang);
                         handle.setField("spoken_languages", updated);
                       }}
                     >

321-324: Use cn([...]) array style and group classes for vocabulary input

Aligns with guideline and mirrors the spoken-language input container.

-                className={cn(
-                  "flex flex-wrap items-center w-full px-2 py-1.5 gap-1.5 rounded-lg bg-white border border-neutral-200 focus-within:border-neutral-300 min-h-[38px]",
-                  vocabInputFocused && "border-neutral-300",
-                )}
+                className={cn([
+                  "flex flex-wrap items-center w-full min-h-[38px] px-2 py-1.5 gap-1.5",
+                  "rounded-lg bg-white border border-neutral-200 focus-within:border-neutral-300",
+                  vocabInputFocused && "border-neutral-300",
+                ])}

As per coding guidelines


334-347: Add aria-label to remove-vocabulary button

Accessibility improvement.

                     <Button
                       type="button"
                       variant="ghost"
                       size="sm"
                       className="h-3 w-3 p-0 hover:bg-transparent ml-0.5"
+                      aria-label={`Remove ${vocab}`}
                       onClick={(e) => {
                         e.stopPropagation();
                         const updated = (value.jargons ?? []).filter(j => j !== vocab);
                         handle.setField("jargons", updated);
                       }}
                     >

214-216: Preserve visible focus styles on SelectTrigger

Avoid removing focus ring to maintain keyboard visibility.

-              <SelectTrigger className="w-40 shadow-none focus:ring-0 focus:ring-offset-0">
+              <SelectTrigger className="w-40 shadow-none">

236-236: Prefer refs over document.getElementById for focusing inputs

Simplifies and avoids id coupling.

I can provide a small refactor using useRef() for both inputs if helpful.


96-129: Optional: use named React event types to avoid React namespace

If you prefer to remove the React namespace entirely, import types and update signatures.

+import type { KeyboardEvent, ChangeEvent } from "react";

-const handleLanguageKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
+const handleLanguageKeyDown = (e: KeyboardEvent<HTMLInputElement>) => {

-const handleVocabChange = (e: React.ChangeEvent<HTMLInputElement>) => {
+const handleVocabChange = (e: ChangeEvent<HTMLInputElement>) => {

-const handleVocabKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
+const handleVocabKeyDown = (e: KeyboardEvent<HTMLInputElement>) => {

Also applies to: 148-168, 131-147

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ba854b9 and 81c8a6c.

📒 Files selected for processing (2)
  • apps/desktop/src/components/settings/general.tsx (7 hunks)
  • apps/desktop/src/routes/app/settings/_layout.tsx (3 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
apps/desktop/**/*.{tsx,jsx}

📄 CodeRabbit inference engine (apps/desktop/.cursor/rules/style.mdc)

apps/desktop/**/*.{tsx,jsx}: When there are many Tailwind classNames with conditional logic, use the utility cn imported as import { cn } from "@hypr/utils"
When using cn for Tailwind classNames, always pass an array
Group Tailwind classNames by logical sections when using cn (split array items by grouping)

Files:

  • apps/desktop/src/routes/app/settings/_layout.tsx
  • apps/desktop/src/components/settings/general.tsx
🧬 Code graph analysis (2)
apps/desktop/src/routes/app/settings/_layout.tsx (1)
packages/utils/src/cn.ts (1)
  • cn (20-22)
apps/desktop/src/components/settings/general.tsx (1)
packages/utils/src/cn.ts (1)
  • cn (20-22)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: ci (macos, macos-14)

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

♻️ Duplicate comments (2)
apps/desktop/src/components/settings/general.tsx (2)

3-3: Import React types to resolve React.KeyboardEvent/React.ChangeEvent usage

Without importing the React namespace or types, TypeScript will error on lines 96, 131, and 148 where React.KeyboardEvent and React.ChangeEvent are used.

Apply this diff:

-import { useMemo, useState } from "react";
+import type React from "react";
+import { useMemo, useState } from "react";

390-390: Fix cn() usage to pass array arguments

Per coding guidelines, all cn() calls must pass Tailwind classNames as an array. Lines 390, 392, and 415 pass strings directly.

As per coding guidelines.

Apply these diffs:

-              <div className={cn(!hasMicrophoneAccess && "flex items-center gap-2 mb-1")}>
+              <div className={cn([!hasMicrophoneAccess && "flex items-center gap-2 mb-1"])}>
-                <h3 className={cn("text-sm font-medium mb-1", !hasMicrophoneAccess && "text-red-500")}>
+                <h3 className={cn(["text-sm font-medium mb-1", !hasMicrophoneAccess && "text-red-500"])}>
-              <div className={cn(!hasSystemAudioAccess && "flex items-center gap-2")}>
+              <div className={cn([!hasSystemAudioAccess && "flex items-center gap-2"])}>

Also applies to: 392-392, 415-415

🧹 Nitpick comments (1)
apps/desktop/src/components/settings/general.tsx (1)

69-80: Replace mock permission handlers with real system API calls

The mock permission states and toggle handlers are placeholder code. Real implementation should request microphone and system audio access via Electron or Tauri APIs.

Do you want me to generate the real permission request implementation or open a new issue to track this task?

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 81c8a6c and 8c8c1bc.

📒 Files selected for processing (2)
  • apps/desktop/src/components/settings/general.tsx (7 hunks)
  • apps/desktop/src/routes/app/settings/_layout.tsx (3 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
apps/desktop/**/*.{tsx,jsx}

📄 CodeRabbit inference engine (apps/desktop/.cursor/rules/style.mdc)

apps/desktop/**/*.{tsx,jsx}: When there are many Tailwind classNames with conditional logic, use the utility cn imported as import { cn } from "@hypr/utils"
When using cn for Tailwind classNames, always pass an array
Group Tailwind classNames by logical sections when using cn (split array items by grouping)

Files:

  • apps/desktop/src/routes/app/settings/_layout.tsx
  • apps/desktop/src/components/settings/general.tsx
🧠 Learnings (3)
📚 Learning: 2025-10-21T03:03:20.070Z
Learnt from: CR
PR: fastrepl/hyprnote#0
File: apps/desktop/.cursor/rules/style.mdc:0-0
Timestamp: 2025-10-21T03:03:20.070Z
Learning: Applies to apps/desktop/**/*.{tsx,jsx} : When using `cn` for Tailwind classNames, always pass an array

Applied to files:

  • apps/desktop/src/components/settings/general.tsx
📚 Learning: 2025-10-21T03:03:20.070Z
Learnt from: CR
PR: fastrepl/hyprnote#0
File: apps/desktop/.cursor/rules/style.mdc:0-0
Timestamp: 2025-10-21T03:03:20.070Z
Learning: Applies to apps/desktop/**/*.{tsx,jsx} : When there are many Tailwind classNames with conditional logic, use the utility `cn` imported as `import { cn } from "hypr/utils"`

Applied to files:

  • apps/desktop/src/components/settings/general.tsx
📚 Learning: 2025-10-21T03:03:20.070Z
Learnt from: CR
PR: fastrepl/hyprnote#0
File: apps/desktop/.cursor/rules/style.mdc:0-0
Timestamp: 2025-10-21T03:03:20.070Z
Learning: Applies to apps/desktop/**/*.{tsx,jsx} : Group Tailwind classNames by logical sections when using `cn` (split array items by grouping)

Applied to files:

  • apps/desktop/src/components/settings/general.tsx
🧬 Code graph analysis (2)
apps/desktop/src/routes/app/settings/_layout.tsx (1)
packages/utils/src/cn.ts (1)
  • cn (20-22)
apps/desktop/src/components/settings/general.tsx (1)
packages/utils/src/cn.ts (1)
  • cn (20-22)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: zizmor
  • GitHub Check: ci (macos, macos-14)
🔇 Additional comments (6)
apps/desktop/src/routes/app/settings/_layout.tsx (2)

47-47: Past review issue resolved: aside width is now correct.

The previous invalid max-w-52 class has been properly replaced with w-52 (13rem/208px), and flex-1 has been removed. The aside now maintains a fixed width as intended.


98-109: Verify that overriding the ghost variant styles is intentional.

The Button uses variant="ghost" but then overrides its default hover behavior with custom classes (hover:bg-neutral-200 and shadow-none). While this may be intentional for a consistent neutral design, it could conflict with the Button component's design system.

Please confirm:

  1. Is the shadow-none class necessary, or does the ghost variant already have no shadow?
  2. Is overriding the ghost variant's hover style with hover:bg-neutral-200 the intended design?

If this custom styling is intentional and differs from the design system, consider documenting why these overrides are needed or creating a dedicated button variant for navigation items.

apps/desktop/src/components/settings/general.tsx (4)

82-129: LGTM!

The filtered language logic and keyboard navigation are well-implemented. The handler correctly manages Backspace to remove the last language, Arrow keys for dropdown navigation, Enter to add, and Escape to close.


131-168: LGTM!

The vocabulary input handlers correctly parse comma-separated terms, filter duplicates, and support intuitive keyboard shortcuts (Backspace to remove last, Enter to add, Escape to close).


265-327: Excellent accessibility improvements!

The combobox now has proper ARIA attributes (role="combobox", aria-haspopup, aria-expanded, aria-controls, aria-activedescendant), the dropdown has role="listbox", and options have role="option" with aria-selected. The onMouseDown preventDefault pattern correctly prevents blur on option click.

These changes fully address the previous review feedback on accessibility.


330-381: LGTM!

The custom vocabulary input has a clean token-based UI with proper ARIA labeling. The comma-separated input pattern is intuitive and consistent with the language input styling.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

♻️ Duplicate comments (1)
apps/desktop/src/components/settings/general.tsx (1)

3-3: Import React types to resolve React.KeyboardEvent/ChangeEvent usage.

The file uses React.KeyboardEvent (line 96) and React.ChangeEvent (line 131) without importing the React namespace, causing TypeScript errors.

Apply this diff:

-import { useMemo, useState } from "react";
+import type React from "react";
+import { useMemo, useState } from "react";
🧹 Nitpick comments (6)
apps/desktop/src/components/main/sidebar/profile/index.tsx (1)

120-130: Consider making the keyboard symbol in the badge platform-aware for better UX.

The keyboard shortcut handler is properly implemented in apps/desktop/src/contexts/shell/settings.ts (lines 11-15) and correctly handles both metaKey (Cmd on macOS) and ctrlKey (Ctrl on Windows/Linux). However, the badge displays a hardcoded symbol which will appear incorrect to Windows/Linux users who trigger the shortcut with Ctrl+,.

Consider updating the badge to show the appropriate symbol for each platform:

const isMac = navigator.platform.toUpperCase().indexOf('MAC') >= 0;
const modifierKey = isMac ? '⌘' : 'Ctrl';

badge: (
  <KbdGroup>
    <Kbd className="bg-neutral-200">{modifierKey}</Kbd>
    <Kbd className="bg-neutral-200">,</Kbd>
  </KbdGroup>
)
apps/desktop/src/contexts/shell/settings.ts (1)

6-8: Harden shortcut handling and catch plugin errors

  • Don’t trigger while typing; guard inputs/contentEditable and event.repeat.
  • Normalize detection to event.code (fallback to key).
  • Catch windowShow promise errors to avoid unhandled rejections.

Apply:

 import { useCallback, useEffect } from "react";
 
 export function useSettings() {
   const openSettings = useCallback(() => {
-    windowsCommands.windowShow({ type: "settings" });
+    void windowsCommands
+      .windowShow({ type: "settings" })
+      .catch((e) => console.error("openSettings failed:", e));
   }, []);
 
   useEffect(() => {
-    const handleKeyDown = (event: KeyboardEvent) => {
-      if ((event.metaKey || event.ctrlKey) && event.key === ",") {
-        event.preventDefault();
-        openSettings();
-      }
-    };
+    const isEditableTarget = (el: EventTarget | null) => {
+      if (!(el instanceof Element)) return false;
+      const tag = el.tagName.toLowerCase();
+      return el.getAttribute("contenteditable") === "true" || ["input", "textarea", "select"].includes(tag);
+    };
+    const handleKeyDown = (event: KeyboardEvent) => {
+      if (event.defaultPrevented || event.repeat) return;
+      if (isEditableTarget(event.target)) return;
+      const isMod = event.metaKey || event.ctrlKey;
+      if (isMod && (event.code === "Comma" || event.key === ",")) {
+        event.preventDefault();
+        openSettings();
+      }
+    };
 
     window.addEventListener("keydown", handleKeyDown);
     return () => window.removeEventListener("keydown", handleKeyDown);
   }, [openSettings]);

Also applies to: 10-20

apps/desktop/src/components/main/body/search.tsx (1)

14-18: Unregister focus callback on unmount

Have registerFocusCallback return an unsubscribe to prevent stale refs if this component unmounts/remounts.

After updating the API (see contexts/search/ui.tsx comment), adjust here:

-  useEffect(() => {
-    registerFocusCallback(() => {
-      inputRef.current?.focus();
-    });
-  }, [registerFocusCallback]);
+  useEffect(() => {
+    const unregister = registerFocusCallback(() => {
+      inputRef.current?.focus();
+    });
+    return () => unregister?.();
+  }, [registerFocusCallback]);

Also: cn usage with arrays looks good.

apps/desktop/src/contexts/shell/search.ts (1)

8-18: Avoid firing Cmd/Ctrl+K while typing; normalize key detection

Guard editable targets and repeats; prefer event.code to handle layout/Shift differences.

 export function useSearchShortcut({ onFocusSearch }: UseSearchShortcutProps) {
   useEffect(() => {
-    const handleKeyDown = (event: KeyboardEvent) => {
-      if ((event.metaKey || event.ctrlKey) && event.key === "k") {
-        event.preventDefault();
-        onFocusSearch();
-      }
-    };
+    const isEditableTarget = (el: EventTarget | null) => {
+      if (!(el instanceof Element)) return false;
+      const tag = el.tagName.toLowerCase();
+      return el.getAttribute("contenteditable") === "true" || ["input", "textarea", "select"].includes(tag);
+    };
+    const handleKeyDown = (event: KeyboardEvent) => {
+      if (event.defaultPrevented || event.repeat) return;
+      if (isEditableTarget(event.target)) return;
+      const isMod = event.metaKey || event.ctrlKey;
+      if (isMod && (event.code === "KeyK" || event.key.toLowerCase() === "k")) {
+        event.preventDefault();
+        onFocusSearch();
+      }
+    };

Consider extracting isEditableTarget to a shared util to DRY across listeners.

apps/desktop/src/contexts/shell/index.tsx (1)

25-36: Memoize ShellContext value to reduce re-renders

Wrap the provider value in useMemo to stabilize references.

-import { createContext, useContext } from "react";
+import { createContext, useContext, useMemo } from "react";
@@
-  return (
-    <ShellContext.Provider value={{ chat, leftsidebar, search, settings, tabs }}>
-      {children}
-    </ShellContext.Provider>
-  );
+  const value = useMemo(() => ({ chat, leftsidebar, search, settings, tabs }), [
+    chat,
+    leftsidebar,
+    search,
+    settings,
+    tabs,
+  ]);
+  return <ShellContext.Provider value={value}>{children}</ShellContext.Provider>;
apps/desktop/src/contexts/search/ui.tsx (1)

30-43: Return an unsubscribe from registerFocusCallback to avoid stale callbacks

Provide a cleanup path and update the type.

 interface SearchUIContextValue {
@@
-  registerFocusCallback: (callback: () => void) => void;
+  registerFocusCallback: (callback: () => void) => () => void;
   focusInput: () => void;
 }
@@
-  const registerFocusCallback = useCallback((callback: () => void) => {
-    focusCallbackRef.current = callback;
-  }, []);
+  const registerFocusCallback = useCallback((callback: () => void) => {
+    focusCallbackRef.current = callback;
+    return () => {
+      if (focusCallbackRef.current === callback) {
+        focusCallbackRef.current = null;
+      }
+    };
+  }, []);
@@
   const value = useMemo(
     () => ({
@@
       onBlur,
       registerFocusCallback,
       focusInput,
     }),
-    [query, filters, results, isSearching, isFocused, isIndexing, onFocus, onBlur, registerFocusCallback, focusInput],
+    [query, filters, results, isSearching, isFocused, isIndexing, onFocus, onBlur, registerFocusCallback, focusInput],
   );

Optionally also clear on provider unmount:

useEffect(() => () => { focusCallbackRef.current = null; }, []);

Update callers (e.g., search.tsx) to use the returned unsubscribe. Based on learnings.

Also applies to: 153-153, 201-208, 228-232

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 8c8c1bc and 8b70bab.

📒 Files selected for processing (11)
  • apps/desktop/src/components/main/body/index.tsx (0 hunks)
  • apps/desktop/src/components/main/body/search.tsx (1 hunks)
  • apps/desktop/src/components/main/sidebar/profile/index.tsx (2 hunks)
  • apps/desktop/src/components/settings/general.tsx (7 hunks)
  • apps/desktop/src/contexts/search/ui.tsx (5 hunks)
  • apps/desktop/src/contexts/shell/index.tsx (1 hunks)
  • apps/desktop/src/contexts/shell/search.ts (1 hunks)
  • apps/desktop/src/contexts/shell/settings.ts (1 hunks)
  • apps/desktop/src/contexts/shell/tabs.ts (1 hunks)
  • apps/desktop/src/routes/app/main/_layout.tsx (3 hunks)
  • packages/tiptap/src/editor/index.tsx (1 hunks)
💤 Files with no reviewable changes (1)
  • apps/desktop/src/components/main/body/index.tsx
🧰 Additional context used
📓 Path-based instructions (1)
apps/desktop/**/*.{tsx,jsx}

📄 CodeRabbit inference engine (apps/desktop/.cursor/rules/style.mdc)

apps/desktop/**/*.{tsx,jsx}: When there are many Tailwind classNames with conditional logic, use the utility cn imported as import { cn } from "@hypr/utils"
When using cn for Tailwind classNames, always pass an array
Group Tailwind classNames by logical sections when using cn (split array items by grouping)

Files:

  • apps/desktop/src/contexts/shell/index.tsx
  • apps/desktop/src/routes/app/main/_layout.tsx
  • apps/desktop/src/components/main/sidebar/profile/index.tsx
  • apps/desktop/src/components/settings/general.tsx
  • apps/desktop/src/contexts/search/ui.tsx
  • apps/desktop/src/components/main/body/search.tsx
🧬 Code graph analysis (5)
apps/desktop/src/contexts/shell/index.tsx (3)
apps/desktop/src/contexts/shell/search.ts (1)
  • useSearchShortcut (7-21)
apps/desktop/src/contexts/shell/settings.ts (1)
  • useSettings (5-23)
apps/desktop/src/contexts/shell/tabs.ts (1)
  • useTabsShortcuts (6-48)
apps/desktop/src/routes/app/main/_layout.tsx (2)
apps/desktop/src/contexts/search/ui.tsx (2)
  • SearchUIProvider (144-235)
  • useSearch (237-243)
apps/desktop/src/contexts/shell/index.tsx (1)
  • ShellProvider (25-37)
apps/desktop/src/contexts/shell/tabs.ts (1)
plugins/windows/src/ext.rs (1)
  • close (56-62)
apps/desktop/src/components/settings/general.tsx (1)
packages/utils/src/cn.ts (1)
  • cn (20-22)
apps/desktop/src/components/main/body/search.tsx (1)
apps/desktop/src/contexts/search/ui.tsx (1)
  • useSearch (237-243)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: ci (macos, macos-14)
🔇 Additional comments (8)
packages/tiptap/src/editor/index.tsx (2)

70-77: Backspace prevention at document start looks correct.

The implementation properly prevents backspace when the cursor is at the start of the document with no selection. The logic correctly uses ProseMirror's selection API to check both position and selection state.

Please confirm this is the intended user experience. Users might expect to be able to press backspace at the document start (even if it has no effect), and some might find preventDefault disorienting if they habitually press backspace.


79-82: Verify that completely disabling Tab is intentional.

This unconditionally prevents the Tab key from functioning in the editor. While this might be intentional for accessibility (preserving Tab for keyboard navigation) or application-specific UX requirements, users typically expect Tab to work in text editors for indentation.

Please confirm:

  1. Is Tab intentionally disabled to keep focus navigation working?
  2. Should Tab insert indentation/spaces when the editor is focused?
  3. Are there alternative keyboard shortcuts for indentation if needed?

If Tab should remain disabled for navigation, consider adding a comment explaining the rationale.

apps/desktop/src/components/main/sidebar/profile/index.tsx (1)

2-2: LGTM!

The import is correctly used in the Settings menu item badge below.

apps/desktop/src/components/settings/general.tsx (4)

96-129: LGTM! Keyboard navigation and ARIA semantics correctly implemented.

The language picker keyboard handler and ARIA attributes align with accessibility best practices. The combobox pattern is properly implemented with aria-haspopup, aria-expanded, aria-controls, and aria-activedescendant.

Also applies to: 265-287


289-326: LGTM! Listbox semantics and click handling correctly implemented.

The dropdown properly implements role="listbox" and role="option" with aria-selected. The onMouseDown preventDefault pattern correctly prevents input blur when selecting options.


131-168: LGTM! Vocabulary input handles comma-separated terms correctly.

The vocabulary input logic properly splits comma-separated terms, deduplicates against existing jargons, and supports keyboard shortcuts (Backspace to remove last, Enter to add current term).

Also applies to: 330-381


388-403: LGTM! PermissionRow extraction eliminates duplication.

The PermissionRow component successfully consolidates the microphone and system audio permission sections, improving maintainability as suggested in previous reviews.

Also applies to: 408-445

apps/desktop/src/routes/app/main/_layout.tsx (1)

52-80: New-tab flow and provider wiring look solid

Session creation + optional close current before opening new is clear; SearchShortcutBridge cleanly bridges focusInput to ShellProvider.

Also applies to: 82-91

@yujonglee yujonglee changed the title settings - 1 General settings improvements Oct 21, 2025
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

♻️ Duplicate comments (5)
packages/tiptap/src/editor/index.tsx (1)

64-68: The previous review comment regarding centralization of global shortcuts still applies.

The hardcoded allowedGlobalShortcuts array remains unchanged and should be extracted to a shared constant as suggested in the previous review.

apps/desktop/src/contexts/shell/tabs.ts (1)

9-45: Unresolved: Keyboard handler still hijacks shortcuts in editable elements.

The past review comment about this code remains valid. The handler currently:

  • Intercepts Cmd/Ctrl shortcuts even when typing in inputs/textareas/contentEditable elements
  • Lacks a repeat guard (event.repeat)
  • Doesn't handle potential errors from await appWindow.close()

These issues should be addressed to prevent disrupting user input in forms and editors throughout the app.

apps/desktop/src/components/settings/general.tsx (3)

3-3: Unresolved: Missing React namespace import for type annotations.

The file uses React.KeyboardEvent and React.ChangeEvent (lines 96, 131) but doesn't import the React namespace, which will cause TypeScript errors.

Apply this diff:

-import { useMemo, useState } from "react";
+import type React from "react";
+import { useMemo, useState } from "react";

68-80: Unresolved: Replace mock permission states with real API integration.

The permission states remain mocked with useState toggles. The past review comment provided detailed guidance on integrating listenerCommands from @hypr/plugin-listener (similar to the onboarding component).

This should be addressed to provide functional permission management.


424-424: Fix cn() to use array format.

Line 424 passes arguments directly to cn() instead of wrapping them in an array, violating the coding guideline that requires all cn() calls to use the array format.

As per coding guidelines. Apply this diff:

-        <h3 className={cn("text-sm font-medium mb-1", !hasAccess && "text-red-500")}>
+        <h3 className={cn(["text-sm font-medium mb-1", !hasAccess && "text-red-500"])}>
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 8b70bab and 758e32d.

📒 Files selected for processing (11)
  • apps/desktop/src/components/main/body/index.tsx (0 hunks)
  • apps/desktop/src/components/main/body/search.tsx (1 hunks)
  • apps/desktop/src/components/main/sidebar/profile/index.tsx (2 hunks)
  • apps/desktop/src/components/settings/general.tsx (7 hunks)
  • apps/desktop/src/contexts/search/ui.tsx (5 hunks)
  • apps/desktop/src/contexts/shell/index.tsx (1 hunks)
  • apps/desktop/src/contexts/shell/search.ts (1 hunks)
  • apps/desktop/src/contexts/shell/settings.ts (1 hunks)
  • apps/desktop/src/contexts/shell/tabs.ts (1 hunks)
  • apps/desktop/src/routes/app/main/_layout.tsx (3 hunks)
  • packages/tiptap/src/editor/index.tsx (1 hunks)
💤 Files with no reviewable changes (1)
  • apps/desktop/src/components/main/body/index.tsx
🚧 Files skipped from review as they are similar to previous changes (2)
  • apps/desktop/src/contexts/shell/index.tsx
  • apps/desktop/src/contexts/search/ui.tsx
🧰 Additional context used
📓 Path-based instructions (1)
apps/desktop/**/*.{tsx,jsx}

📄 CodeRabbit inference engine (apps/desktop/.cursor/rules/style.mdc)

apps/desktop/**/*.{tsx,jsx}: When there are many Tailwind classNames with conditional logic, use the utility cn imported as import { cn } from "@hypr/utils"
When using cn for Tailwind classNames, always pass an array
Group Tailwind classNames by logical sections when using cn (split array items by grouping)

Files:

  • apps/desktop/src/components/main/body/search.tsx
  • apps/desktop/src/components/settings/general.tsx
  • apps/desktop/src/components/main/sidebar/profile/index.tsx
  • apps/desktop/src/routes/app/main/_layout.tsx
🧠 Learnings (3)
📚 Learning: 2025-10-21T03:03:20.070Z
Learnt from: CR
PR: fastrepl/hyprnote#0
File: apps/desktop/.cursor/rules/style.mdc:0-0
Timestamp: 2025-10-21T03:03:20.070Z
Learning: Applies to apps/desktop/**/*.{tsx,jsx} : When using `cn` for Tailwind classNames, always pass an array

Applied to files:

  • apps/desktop/src/components/settings/general.tsx
📚 Learning: 2025-10-21T03:03:20.070Z
Learnt from: CR
PR: fastrepl/hyprnote#0
File: apps/desktop/.cursor/rules/style.mdc:0-0
Timestamp: 2025-10-21T03:03:20.070Z
Learning: Applies to apps/desktop/**/*.{tsx,jsx} : When there are many Tailwind classNames with conditional logic, use the utility `cn` imported as `import { cn } from "hypr/utils"`

Applied to files:

  • apps/desktop/src/components/settings/general.tsx
📚 Learning: 2025-10-21T03:03:20.070Z
Learnt from: CR
PR: fastrepl/hyprnote#0
File: apps/desktop/.cursor/rules/style.mdc:0-0
Timestamp: 2025-10-21T03:03:20.070Z
Learning: Applies to apps/desktop/**/*.{tsx,jsx} : Group Tailwind classNames by logical sections when using `cn` (split array items by grouping)

Applied to files:

  • apps/desktop/src/components/settings/general.tsx
🧬 Code graph analysis (4)
apps/desktop/src/components/main/body/search.tsx (1)
apps/desktop/src/contexts/search/ui.tsx (1)
  • useSearch (237-243)
apps/desktop/src/contexts/shell/tabs.ts (1)
plugins/windows/src/ext.rs (1)
  • close (56-62)
apps/desktop/src/components/settings/general.tsx (2)
packages/utils/src/cn.ts (1)
  • cn (20-22)
apps/desktop/src/components/main/body/search.tsx (1)
  • Search (7-74)
apps/desktop/src/routes/app/main/_layout.tsx (2)
apps/desktop/src/contexts/search/ui.tsx (2)
  • SearchUIProvider (144-235)
  • useSearch (237-243)
apps/desktop/src/contexts/shell/index.tsx (1)
  • ShellProvider (25-37)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: ci (macos, macos-14)
🔇 Additional comments (10)
packages/tiptap/src/editor/index.tsx (2)

70-77: LGTM!

The backspace handling logic correctly prevents deletion at the document start when the selection is empty. The implementation properly checks cursor position and uses the standard event handling pattern.


79-82: Verify that tab trapping doesn't hinder keyboard navigation.

Unconditionally preventing the default tab behavior may trap keyboard focus within the editor, potentially affecting accessibility for keyboard-only users. Ensure there's an alternative keyboard mechanism (such as Escape) to exit the editor.

apps/desktop/src/components/main/sidebar/profile/index.tsx (1)

2-2: LGTM!

The import statement is correct and the components are properly used in the Settings menu item badge.

apps/desktop/src/contexts/shell/settings.ts (1)

5-23: LGTM!

The hook correctly memoizes openSettings with useCallback and the effect dependency is stable. The keyboard shortcut registration is clean and follows the same pattern as the search hook.

apps/desktop/src/routes/app/main/_layout.tsx (2)

52-71: LGTM!

The handleNewTab callback correctly:

  • Persists a new session row with required fields
  • Optionally closes the current tab before opening a new one
  • Opens a new session tab with the appropriate state

The logic is clear and the dependencies are correctly specified.


82-93: LGTM!

The SearchShortcutBridge component appropriately bridges the search context and shell provider, extracting focusInput from the search context and passing it to the ShellProvider as onFocusSearch. The provider nesting is correct.

apps/desktop/src/components/settings/general.tsx (2)

230-327: LGTM!

The inline language search panel is well-implemented with:

  • Proper keyboard navigation (ArrowUp/Down, Enter, Escape, Backspace)
  • ARIA attributes for accessibility (role, aria-haspopup, aria-expanded, aria-controls, aria-activedescendant)
  • Clean focus management with onMouseDown to prevent blur
  • Correct cn() array usage throughout

The UX improvement over the previous popover approach is significant.


330-381: LGTM!

The vocabulary input provides a clean token-based interface with:

  • Comma-separated term support
  • Backspace to remove last term
  • Enter to add a term
  • Proper duplicate prevention
  • Consistent styling with the language panel
apps/desktop/src/components/main/body/search.tsx (1)

14-18: Code is correct—registerFocusCallback is properly memoized.

The registerFocusCallback is defined with useCallback and an empty dependency array [], making it stable across renders. The effect will not unnecessarily re-run on every render.

apps/desktop/src/contexts/shell/search.ts (1)

7-21: No action required - onFocusSearch is properly memoized.

The callback is wrapped with useCallback(..., []) in SearchUIProvider (line 206 of apps/desktop/src/contexts/search/ui.tsx), giving it a stable reference that persists across renders. This prevents unnecessary re-registration of the event listener in useSearchShortcut.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (2)
apps/desktop/src/components/main/body/index.tsx (2)

8-9: Imports look good; cn usage matches our guidelines.

You’re importing cn and using it with array form downstream. Consider grouping array items by logical sections for readability (base layout, conditional paddings, scrollbars).

As per coding guidelines.


45-46: Shortcut hook colocated in Header is fine; minor nit on unused ref.

useTabsShortcuts() here ensures lifecycle matches Header. Note: tabsScrollContainerRef is currently unused—safe to remove later.

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a6f937f and c9fe2cf.

📒 Files selected for processing (6)
  • apps/desktop/src/components/main/body/index.tsx (4 hunks)
  • apps/desktop/src/components/main/body/search.tsx (2 hunks)
  • apps/desktop/src/contexts/search/ui.tsx (4 hunks)
  • apps/desktop/src/contexts/shell/index.tsx (1 hunks)
  • apps/desktop/src/contexts/shell/settings.ts (1 hunks)
  • apps/desktop/src/routes/app/main/_layout.tsx (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
  • apps/desktop/src/contexts/search/ui.tsx
  • apps/desktop/src/contexts/shell/settings.ts
  • apps/desktop/src/contexts/shell/index.tsx
🧰 Additional context used
📓 Path-based instructions (1)
apps/desktop/**/*.{tsx,jsx}

📄 CodeRabbit inference engine (apps/desktop/.cursor/rules/style.mdc)

apps/desktop/**/*.{tsx,jsx}: When there are many Tailwind classNames with conditional logic, use the utility cn imported as import { cn } from "@hypr/utils"
When using cn for Tailwind classNames, always pass an array
Group Tailwind classNames by logical sections when using cn (split array items by grouping)

Files:

  • apps/desktop/src/components/main/body/search.tsx
  • apps/desktop/src/routes/app/main/_layout.tsx
  • apps/desktop/src/components/main/body/index.tsx
🧬 Code graph analysis (3)
apps/desktop/src/components/main/body/search.tsx (1)
apps/desktop/src/contexts/search/ui.tsx (1)
  • useSearch (219-225)
apps/desktop/src/routes/app/main/_layout.tsx (2)
apps/desktop/src/contexts/search/ui.tsx (1)
  • SearchUIProvider (141-217)
apps/desktop/src/contexts/shell/index.tsx (1)
  • ShellProvider (15-25)
apps/desktop/src/components/main/body/index.tsx (1)
plugins/windows/src/ext.rs (1)
  • close (56-62)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: ci (macos, macos-14)
🔇 Additional comments (7)
apps/desktop/src/components/main/body/search.tsx (3)

2-2: LGTM! Clean separation of focus management concerns.

The refactoring appropriately separates global focus control (via inputRef from context) from local UI state (via isFocused for styling). This allows the context to programmatically focus the input while the component manages its own width animation.

Also applies to: 8-9


30-34: Verify Escape event propagation is intentional.

The Escape handler blurs the input but allows the event to propagate. If other handlers up the component tree also listen for Escape (e.g., to close modals or panels), they will fire as well. Verify this is the intended behavior.

If the Escape should only blur the input without triggering parent handlers, apply this diff:

 onKeyDown={(e) => {
   if (e.key === "Escape") {
+    e.preventDefault();
+    e.stopPropagation();
     e.currentTarget.blur();
   }
 }}

35-36: LGTM! Focus handlers are correctly implemented.

The inline handlers appropriately sync the local isFocused state with the actual focus state of the input element, enabling the width transition effect.

apps/desktop/src/routes/app/main/_layout.tsx (1)

53-62: Provider reordering is valid and introduces no breaking changes.

The new hierarchy correctly maintains all dependencies: SearchUIProvider depends on SearchEngineProvider (satisfied), and ShellProvider has no dependencies on SearchEngine or SearchUI contexts. All useShell() consumers remain properly nested inside ShellProvider, and the reordering allows components within the Shell subtree to access SearchUI context if needed in the future (e.g., for settings improvements). No circular dependencies or scope violations detected.

apps/desktop/src/components/main/body/index.tsx (3)

41-41: Tabs actions wiring LGTM.

Exposing closeOthers/closeAll to TabItem is clean and keeps Header lean.


43-43: Good: central new-tab handler.

useNewTab() centralizes session creation and keeps Header stateless.


355-378: The review comment fundamentally misunderstands TinyBase's API and suggests incorrect changes.

TinyBase's setRow() method is synchronous and returns this (for method chaining), not a Promise. The current code correctly calls setRow() without await. This pattern is consistent throughout the entire codebase. The suggested diff to make handler async and await persistedStore.setRow() contradicts TinyBase's actual API and would not work.

The optional chaining (persistedStore?.setRow()) is a valid guard that gracefully handles cases where persistedStore is unavailable—a pattern already accepted in the codebase. No changes are needed.

Likely an incorrect or invalid review comment.

@yujonglee yujonglee merged commit ca51d73 into main Oct 21, 2025
13 checks passed
@yujonglee yujonglee deleted the jj-branch-24 branch October 21, 2025 09:19
@coderabbitai coderabbitai bot mentioned this pull request Nov 15, 2025
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.

3 participants