Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 42 additions & 0 deletions apps/desktop/src/components/main/body/ai.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
X,
} from "lucide-react";
import { useCallback, useMemo, useRef, useState } from "react";
import { useHotkeys } from "react-hotkeys-hook";

import type { ChatShortcut } from "@hypr/store";
import { Button } from "@hypr/ui/components/ui/button";
Expand All @@ -18,6 +19,7 @@ import {
} from "@hypr/ui/components/ui/scroll-fade";
import { cn } from "@hypr/utils";

import { useSettingsNavigation } from "../../../hooks/useSettingsNavigation";
import * as main from "../../../store/tinybase/store/main";
import { type Tab, useTabs } from "../../../store/zustand/tabs";
import { LLM } from "../../settings/ai/llm";
Expand Down Expand Up @@ -97,6 +99,46 @@ function AIView({ tab }: { tab: Extract<Tab, { type: "ai" }> }) {
[updateAiTabState, tab],
);

const enabledMenuKeys: AITabKey[] = [
"transcription",
"intelligence",
"templates",
"shortcuts",
];
const currentIndex = enabledMenuKeys.indexOf(activeTab);

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],
);
Comment on lines +110 to +138
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.


useSettingsNavigation(ref, activeTab);

const menuItems: Array<{
key: AITabKey;
label: string;
Expand Down
36 changes: 36 additions & 0 deletions apps/desktop/src/components/main/body/settings.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
UserIcon,
} from "lucide-react";
import { useCallback, useRef } from "react";
import { useHotkeys } from "react-hotkeys-hook";

import { Button } from "@hypr/ui/components/ui/button";
import {
Expand All @@ -15,6 +16,7 @@ import {
} from "@hypr/ui/components/ui/scroll-fade";
import { cn } from "@hypr/utils";

import { useSettingsNavigation } from "../../../hooks/useSettingsNavigation";
import {
type SettingsTab,
type Tab,
Expand Down Expand Up @@ -96,6 +98,40 @@ function SettingsView({ tab }: { tab: Extract<Tab, { type: "settings" }> }) {
[updateSettingsTabState, tab],
);

const currentIndex = SECTIONS.findIndex((s) => s.id === activeTab);

useHotkeys(
"ctrl+alt+left",
() => {
if (currentIndex > 0) {
setActiveTab(SECTIONS[currentIndex - 1].id);
}
},
{
preventDefault: true,
enableOnFormTags: true,
enableOnContentEditable: true,
},
[currentIndex, setActiveTab],
);

useHotkeys(
"ctrl+alt+right",
() => {
if (currentIndex < SECTIONS.length - 1) {
setActiveTab(SECTIONS[currentIndex + 1].id);
}
},
{
preventDefault: true,
enableOnFormTags: true,
enableOnContentEditable: true,
},
[currentIndex, setActiveTab],
);

useSettingsNavigation(ref, activeTab);

const renderContent = () => {
switch (activeTab) {
case "account":
Expand Down
5 changes: 4 additions & 1 deletion apps/desktop/src/components/settings/general/account.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -422,7 +422,10 @@ function Container({
children?: ReactNode;
}) {
return (
<section className="bg-neutral-50 p-4 rounded-lg flex flex-col gap-4">
<section
data-settings-item
className="bg-neutral-50 p-4 rounded-lg flex flex-col gap-4"
>
<div className="flex flex-col gap-2">
<h1 className="text-md font-semibold font-serif">{title}</h1>
{description && (
Expand Down
8 changes: 6 additions & 2 deletions apps/desktop/src/components/settings/general/app-settings.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -65,12 +65,16 @@ function SettingRow({
onChange: (checked: boolean) => void;
}) {
return (
<div className="flex items-center justify-between gap-4">
<div data-settings-item className="flex items-center justify-between gap-4">
<div className="flex-1">
<h3 className="text-sm font-medium mb-1">{title}</h3>
<p className="text-xs text-neutral-600">{description}</p>
</div>
<Switch checked={checked} onCheckedChange={onChange} />
<Switch
data-settings-activate
checked={checked}
onCheckedChange={onChange}
/>
</div>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,10 @@ export function MainLanguageView({
);

return (
<div className="flex flex-row items-center justify-between">
<div
data-settings-item
className="flex flex-row items-center justify-between"
>
<div>
<h3 className="text-sm font-medium mb-1">Main language</h3>
<p className="text-xs text-neutral-600">
Expand Down
18 changes: 15 additions & 3 deletions apps/desktop/src/components/settings/general/notification.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -237,14 +237,18 @@ export function NotificationSettingsView() {
<div className="flex flex-col gap-6">
<form.Field name="notification_event">
{(field) => (
<div className="flex items-start justify-between gap-4">
<div
data-settings-item
className="flex items-start justify-between gap-4"
>
<div className="flex-1">
<h3 className="mb-1 text-sm font-medium">Event notifications</h3>
<p className="text-xs text-neutral-600">
Get notified 5 minutes before calendar events start
</p>
</div>
<Switch
data-settings-activate
checked={field.state.value}
onCheckedChange={field.handleChange}
/>
Expand All @@ -255,7 +259,10 @@ export function NotificationSettingsView() {
<form.Field name="notification_detect">
{(field) => (
<div className="flex flex-col gap-4">
<div className="flex items-start justify-between gap-4">
<div
data-settings-item
className="flex items-start justify-between gap-4"
>
<div className="flex-1">
<h3 className="mb-1 text-sm font-medium">
Microphone detection
Expand All @@ -266,6 +273,7 @@ export function NotificationSettingsView() {
</p>
</div>
<Switch
data-settings-activate
checked={field.state.value}
onCheckedChange={field.handleChange}
/>
Expand Down Expand Up @@ -415,7 +423,10 @@ export function NotificationSettingsView() {

<form.Field name="respect_dnd">
{(field) => (
<div className="flex items-start justify-between gap-4">
<div
data-settings-item
className="flex items-start justify-between gap-4"
>
<div className="flex-1">
<h3 className="mb-1 text-sm font-medium">
Respect Do-Not-Disturb mode
Expand All @@ -426,6 +437,7 @@ export function NotificationSettingsView() {
</p>
</div>
<Switch
data-settings-activate
checked={field.state.value}
onCheckedChange={field.handleChange}
disabled={!anyNotificationEnabled}
Expand Down
3 changes: 2 additions & 1 deletion apps/desktop/src/components/settings/general/permissions.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ function PermissionRow({
};

return (
<div className="flex items-center justify-between gap-4">
<div data-settings-item className="flex items-center justify-between gap-4">
<div className="flex-1">
<div
className={cn([
Expand Down Expand Up @@ -103,6 +103,7 @@ function PermissionRow({
</div>
</div>
<Button
data-settings-activate
variant={isAuthorized ? "outline" : "default"}
size="icon"
onClick={handleButtonClick}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,7 @@ export function SearchableSelect({
onSelect={() => handleSelect(option.value)}
className={cn([
"cursor-pointer",
"focus:bg-neutral-200! hover:bg-neutral-200! aria-selected:bg-transparent",
"hover:bg-neutral-200! data-[selected=true]:bg-neutral-200!",
])}
Comment on lines +124 to 125
Copy link
Contributor

Choose a reason for hiding this comment

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

🚩 searchable-select removes focus: and aria-selected: styles, relying solely on data-[selected=true]

The CommandItem styling was changed from focus:bg-neutral-200! hover:bg-neutral-200! aria-selected:bg-transparent to hover:bg-neutral-200! data-[selected=true]:bg-neutral-200!. This assumes the Command component (likely from cmdk) sets data-selected="true" on the active item. Modern versions of cmdk do use this attribute, but the old code handled both focus: and aria-selected: states. If the underlying cmdk version uses aria-selected instead of data-selected, the keyboard highlight on items would break. This is likely fine if the project uses cmdk v1+, but worth verifying the cmdk version.

Open in Devin Review

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

>
<span className="truncate flex-1">{option.label}</span>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ export function SpokenLanguagesView({
};

return (
<div>
<div data-settings-item>
<h3 className="text-sm font-medium mb-1">Spoken languages</h3>
<p className="text-xs text-neutral-600 mb-3">
Add other languages you use other than the main language
Expand Down
2 changes: 1 addition & 1 deletion apps/desktop/src/components/settings/general/storage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ function StoragePathRow({
};

return (
<div className="flex items-center gap-3">
<div data-settings-item className="flex items-center gap-3">
<Tooltip delayDuration={0}>
<TooltipTrigger asChild>
<div className="flex items-center gap-2 w-24 shrink-0 cursor-default">
Expand Down
5 changes: 4 additions & 1 deletion apps/desktop/src/components/settings/general/timezone.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,10 @@ export function TimezoneSelector() {
};

return (
<div className="flex flex-row items-center justify-between">
<div
data-settings-item
className="flex flex-row items-center justify-between"
>
<div>
<h3 className="text-sm font-medium mb-1">Timezone</h3>
<p className="text-xs text-neutral-600">
Expand Down
5 changes: 4 additions & 1 deletion apps/desktop/src/components/settings/general/week-start.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,10 @@ export function WeekStartSelector() {
};

return (
<div className="flex flex-row items-center justify-between">
<div
data-settings-item
className="flex flex-row items-center justify-between"
>
<div>
<h3 className="text-sm font-medium mb-1">Week starts on</h3>
<p className="text-xs text-neutral-600">
Expand Down
13 changes: 11 additions & 2 deletions apps/desktop/src/components/settings/lab/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,22 @@ export function SettingsLab() {

return (
<div className="flex flex-col gap-4 pt-3">
<div className="flex items-center justify-between gap-4">
<div
data-settings-item
className="flex items-center justify-between gap-4"
>
<div className="flex-1">
<h3 className="text-sm font-medium mb-1">Control Overlay</h3>
<p className="text-xs text-neutral-600">
Floating window for quick access to recording controls.
</p>
</div>
<Button variant="outline" size="sm" onClick={handleOpenControlWindow}>
<Button
data-settings-activate
variant="outline"
size="sm"
onClick={handleOpenControlWindow}
>
Open
</Button>
</div>
Expand Down Expand Up @@ -80,6 +88,7 @@ function DownloadButtons() {
return (
<div
key={channel}
data-settings-item
className="flex items-center justify-between gap-4"
>
<div className="flex-1">
Expand Down
Loading
Loading