Skip to content

Conversation

@ComputelessComputer
Copy link
Collaborator

• Added date utilities and class name merger
• Updated import path for class names
• Removed unused legacy code
• Removed pushable button component
• Implemented direct implementation

@coderabbitai
Copy link

coderabbitai bot commented Oct 20, 2025

📝 Walkthrough

Walkthrough

The PR consolidates utility functions (cn, date utilities) from the UI package into a centralized @hypr/utils package, updates imports across the desktop application, removes several UI building blocks from the UI package, and refactors some desktop components to inline or replace removed functionality.

Changes

Cohort / File(s) Summary
Dependency additions
apps/desktop/package.json, packages/ui/package.json, packages/utils/package.json
Added @hypr/utils workspace dependency to desktop and UI packages; added clsx (^2.1.1) and tailwind-merge (^2.6.0) to utils package.
New utility modules in @hypr/utils
packages/utils/src/cn.ts, packages/utils/src/date.ts, packages/utils/src/index.ts
Created cn utility (className merger via clsx/tailwind-merge) and date utilities module (formatDate, formatDateRange, getMeetingDomain, date-fns re-exports). Updated index.ts to export new modules.
Removed utilities from @hypr/ui
packages/ui/src/lib/utils.ts
Removed cn utility and its dependencies (clsx, tailwind-merge imports).
Removed UI components from packages/ui
packages/ui/src/components/block/calendar-structure.tsx, chat-panel-button.tsx, event-chip.tsx, listen-button.tsx, meeting-metadata-chip.tsx, participants-chip.tsx, sound-indicator.tsx, tab-header.tsx, title-input.tsx
Deleted nine UI building block components including calendar structure, event/participant chips, listen button, and metadata chip.
cn import migrations (desktop app)
apps/desktop/src/components/chat/{body,header,input,interactive,message/normal,message/shared,trigger}.tsx, apps/desktop/src/components/main/body/{index,search,calendars,contacts/*}.tsx, apps/desktop/src/components/main/body/sessions/{floating/*,note-input/*,outer-header/metadata,title-input}.tsx, apps/desktop/src/components/main/sidebar/{profile/*,search/*,timeline/*}.tsx, apps/desktop/src/components/settings/ai/**/*.tsx, apps/desktop/src/contexts/audio-player/timeline.tsx, apps/desktop/src/devtool/*.tsx, apps/desktop/src/routes/app/{onboarding,settings/_layout}.tsx
Updated 40+ files to import cn from @hypr/utils instead of @hypr/ui/lib/utils.
Date utility import migrations
apps/desktop/src/components/chat/header.tsx, apps/desktop/src/components/chat/message/normal.tsx, apps/desktop/src/store/tinybase/persisted.ts, apps/desktop/src/utils/timeline.ts
Migrated format, formatDistanceToNow, and date-fns utilities to use @hypr/utils or consolidated imports from @hypr/utils.
Refactored components in desktop app
apps/desktop/src/components/main/body/calendars.tsx
Replaced CalendarStructure component usage with inlined div-based calendar layout including month header, navigation buttons (via lucide-react), and day grid.
Refactored session metadata component
apps/desktop/src/components/main/body/sessions/outer-header/metadata.tsx
Replaced MeetingMetadataChip with custom Popover-based UI; added ParticipantChip, ParticipantsSection components inline with participant search, keyboard navigation, and updated event handlers.
Refactored listen session component
apps/desktop/src/components/main/body/sessions/floating/listen.tsx
Replaced SoundIndicator import with DancingSticks; introduced local SoundIndicator component wrapping amplitude computation from provided value prop.
Button component updates
apps/desktop/src/routes/app/onboarding.tsx
Replaced PushableButton with Button component in Welcome and Permissions components.
Removed session components
apps/desktop/src/components/main/body/sessions/outer-header/event.tsx, apps/desktop/src/components/main/body/sessions/outer-header/participant.tsx
Deleted entire SessionEvent component and SessionParticipants component (including helper hook useSessionParticipantGroups) from outer-header module.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Rationale: This PR involves substantial structural changes across 50+ files with heterogeneous patterns: mechanical import migrations (40+ files with consistent changes), multiple UI component deletions (9 components removed from packages/ui), logic-dense refactorings (metadata.tsx, calendars.tsx with new inline rendering), new utility module creation, and removal of significant features (SessionEvent, SessionParticipants components). While many changes are repetitive (import path updates), the component removals, refactorings, and the need to verify inlined replacement logic require careful, diverse reasoning across different file categories.

Possibly related PRs

Suggested reviewers

  • yujonglee
  • duckduckhero

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 5.88% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Title Check ✅ Passed The pull request title "Refactor utilities and cleanup legacy components" accurately summarizes the main changes in the PR. The changeset consolidates utility functions (cn, date utilities) from multiple locations into a centralized @hypr/utils package and removes multiple legacy UI components throughout the codebase. The title is concise, clear, and specific enough that a teammate scanning the history would understand the primary objective without being vague or misleading.
Description Check ✅ Passed The pull request description is related to the changeset and each bullet point corresponds to actual changes present in the diff. The description mentions adding date utilities and class name utilities (implemented in packages/utils), updating import paths (visible across many files), removing legacy code (deleted components), removing the pushable button component (replaced with Button), and implementing direct implementations (inline calendar and metadata components replacing abstracted components). The description provides meaningful information about the changes without being completely off-topic or vague.
✨ 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-23

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.

@yujonglee yujonglee merged commit e07877a into main Oct 20, 2025
3 of 5 checks passed
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: 6

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (5)
apps/desktop/src/components/main/body/calendars.tsx (3)

141-144: Clamp visibleCount to avoid negatives if layout constants change.

Prevents slice with negative indices and rendering glitches.

-  const visibleCount = totalItems > maxPossibleEvents
-    ? maxPossibleEvents - 1
-    : totalItems;
+  const maxVisible = Math.max(maxPossibleEvents - 1, 0);
+  const visibleCount = Math.min(totalItems, maxVisible);

267-274: Use semantic buttons for popover triggers (keyboard/a11y).

div is not keyboard-focusable; switch to button type="button". Improves a11y without behavior change.

-      <PopoverTrigger asChild>
-        <div className="flex items-center space-x-1 px-0.5 py-0.5 cursor-pointer rounded hover:bg-neutral-200 transition-colors h-5">
+      <PopoverTrigger asChild>
+        <button type="button" className="flex items-center space-x-1 px-0.5 py-0.5 cursor-pointer rounded hover:bg-neutral-200 transition-colors h-5">
           <CalendarIcon className="w-2.5 h-2.5 text-neutral-500 flex-shrink-0" />
           <div className="flex-1 text-xs text-neutral-800 truncate">
             {event.title}
           </div>
-        </div>
+        </button>
       </PopoverTrigger>
-      <PopoverTrigger asChild>
-        <div className="flex items-center space-x-1 px-0.5 py-0.5 cursor-pointer rounded hover:bg-neutral-200 transition-colors h-5">
+      <PopoverTrigger asChild>
+        <button type="button" className="flex items-center space-x-1 px-0.5 py-0.5 cursor-pointer rounded hover:bg-neutral-200 transition-colors h-5">
           <FileTextIcon className="w-2.5 h-2.5 text-neutral-500 flex-shrink-0" />
           <div className="flex-1 text-xs text-neutral-800 truncate">
             {session.title}
           </div>
-        </div>
+        </button>
       </PopoverTrigger>
-      <PopoverTrigger asChild>
-        <div className="text-xs text-neutral-600 rounded py-0.5 cursor-pointer hover:bg-neutral-200 px-0.5 h-5">
+      <PopoverTrigger asChild>
+        <button type="button" className="text-xs text-neutral-600 rounded py-0.5 cursor-pointer hover:bg-neutral-200 px-0.5 h-5">
           +{hiddenCount} more
-        </div>
+        </button>
       </PopoverTrigger>

Also applies to: 339-346, 385-387


323-334: Guard against invalid created_at to avoid “Invalid Date/NaN” output.

If session.created_at is missing/invalid, current logic renders broken timestamps.

-  const formatSessionTime = () => {
-    const created = new Date(session.created_at || "");
+  const formatSessionTime = () => {
+    if (!session.created_at) return "";
+    const created = new Date(session.created_at);
+    if (Number.isNaN(created.getTime())) return "";
     const months = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
     const hours = created.getHours();
     const minutes = created.getMinutes();
     const ampm = hours >= 12 ? "PM" : "AM";
     const displayHours = hours % 12 || 12;
apps/desktop/src/components/main/body/sessions/outer-header/metadata.tsx (2)

404-406: Harden window.open against tabnabbing.

Add noopener,noreferrer (and nullify opener) when opening external links.

-  const handleJoinMeeting = useCallback((meetingLink: string) => {
-    window.open(meetingLink, "_blank");
-  }, []);
+  const handleJoinMeeting = useCallback((meetingLink: string) => {
+    const w = window.open(meetingLink, "_blank", "noopener,noreferrer");
+    if (w) w.opener = null;
+  }, []);

376-381: Fix participant search filter to require name OR email match.

Current condition can include non-matching rows when name is missing. Enforce proper matching.

-        if (
-          name && !name.toLowerCase().includes(normalizedQuery)
-          && (!email || !email.toLowerCase().includes(normalizedQuery))
-        ) {
-          return;
-        }
+        const matchesName = name ? name.toLowerCase().includes(normalizedQuery) : false;
+        const matchesEmail = email ? email.toLowerCase().includes(normalizedQuery) : false;
+        if (!matchesName && !matchesEmail) {
+          return;
+        }
🧹 Nitpick comments (5)
packages/utils/src/date.ts (1)

34-54: Custom formatDate implementation is correct.

The lightweight date formatter works correctly with proper token ordering in the regex (dd before d) to avoid matching conflicts. The implementation is well-documented as an intentional alternative to date-fns format() for simple cases.

If you prefer to consolidate on a single formatting approach, consider using the re-exported date-fns format() function directly in formatDateRange (lines 68-69) instead of the custom formatDate. However, the current lightweight implementation is valid and may offer performance benefits for simple format strings.

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

66-72: Add aria-labels to icon-only navigation buttons.

Improves accessibility for screen readers.

-            <Button
+            <Button
               variant="outline"
               className="p-0.5 rounded-none border-none"
+              aria-label="Previous month"
               onClick={handlePreviousMonth}
             >
@@
-            <Button
+            <Button
               variant="outline"
               className="p-0.5 rounded-none border-none"
+              aria-label="Next month"
               onClick={handleNextMonth}
             >

Also applies to: 82-88

apps/desktop/src/components/main/body/sessions/outer-header/metadata.tsx (3)

55-62: Make initials generation robust to extra spaces and short names.

Trim and filter empties to avoid empty/undefined chars.

-  const getInitials = (name: string) => {
-    return name
-      .split(" ")
-      .map((n) => n[0])
-      .join("")
-      .toUpperCase()
-      .slice(0, 2);
-  };
+  const getInitials = (name: string) => {
+    return name
+      .trim()
+      .split(/\s+/)
+      .filter(Boolean)
+      .map((n) => n.charAt(0))
+      .join("")
+      .toUpperCase()
+      .slice(0, 2) || "?";
+  };

101-109: Explicit button type to avoid accidental form submission.

Safe default, minor a11y polish.

-        <button
+        <button
+          type="button"
           onClick={(e) => {
             e.stopPropagation();
             onRemove?.();
           }}
           className="text-red-400 hover:text-red-600 transition-colors opacity-0 group-hover:opacity-100"
         >

218-248: Optional: add ARIA roles for the suggestions popup.

Improve screen reader support by marking listbox/options and active state.

-        {isFocused && searchQuery.trim() && (
-          <div className="absolute top-full left-0 right-0 mt-1 flex flex-col w-full rounded border border-neutral-200 overflow-hidden bg-white shadow-md z-10 max-h-60 overflow-y-auto">
+        {isFocused && searchQuery.trim() && (
+          <div
+            role="listbox"
+            aria-label="Participants"
+            className="absolute top-full left-0 right-0 mt-1 flex flex-col w-full rounded border border-neutral-200 overflow-hidden bg-white shadow-md z-10 max-h-60 overflow-y-auto"
+          >
@@
-                  <button
+                  <button
+                    role="option"
+                    aria-selected={selectedIndex === index}
                     key={participant.id}
                     type="button"
📜 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 4b97be7 and 99174e7.

⛔ Files ignored due to path filters (38)
  • packages/ui/src/components/ui/accordion.tsx is excluded by !packages/ui/src/components/ui/**
  • packages/ui/src/components/ui/avatar.tsx is excluded by !packages/ui/src/components/ui/**
  • packages/ui/src/components/ui/badge.tsx is excluded by !packages/ui/src/components/ui/**
  • packages/ui/src/components/ui/bottom-sheet.tsx is excluded by !packages/ui/src/components/ui/**
  • packages/ui/src/components/ui/breadcrumb.tsx is excluded by !packages/ui/src/components/ui/**
  • packages/ui/src/components/ui/button.tsx is excluded by !packages/ui/src/components/ui/**
  • packages/ui/src/components/ui/card.tsx is excluded by !packages/ui/src/components/ui/**
  • packages/ui/src/components/ui/carousel.tsx is excluded by !packages/ui/src/components/ui/**
  • packages/ui/src/components/ui/checkbox.tsx is excluded by !packages/ui/src/components/ui/**
  • packages/ui/src/components/ui/command.tsx is excluded by !packages/ui/src/components/ui/**
  • packages/ui/src/components/ui/context-menu.tsx is excluded by !packages/ui/src/components/ui/**
  • packages/ui/src/components/ui/cursor-tooltip.tsx is excluded by !packages/ui/src/components/ui/**
  • packages/ui/src/components/ui/dialog.tsx is excluded by !packages/ui/src/components/ui/**
  • packages/ui/src/components/ui/dropdown-menu.tsx is excluded by !packages/ui/src/components/ui/**
  • packages/ui/src/components/ui/form.tsx is excluded by !packages/ui/src/components/ui/**
  • packages/ui/src/components/ui/input-group.tsx is excluded by !packages/ui/src/components/ui/**
  • packages/ui/src/components/ui/input.tsx is excluded by !packages/ui/src/components/ui/**
  • packages/ui/src/components/ui/label.tsx is excluded by !packages/ui/src/components/ui/**
  • packages/ui/src/components/ui/modal.tsx is excluded by !packages/ui/src/components/ui/**
  • packages/ui/src/components/ui/particles.tsx is excluded by !packages/ui/src/components/ui/**
  • packages/ui/src/components/ui/popover.tsx is excluded by !packages/ui/src/components/ui/**
  • packages/ui/src/components/ui/progress.tsx is excluded by !packages/ui/src/components/ui/**
  • packages/ui/src/components/ui/pushable-button.tsx is excluded by !packages/ui/src/components/ui/**
  • packages/ui/src/components/ui/radio-group.tsx is excluded by !packages/ui/src/components/ui/**
  • packages/ui/src/components/ui/resizable.tsx is excluded by !packages/ui/src/components/ui/**
  • packages/ui/src/components/ui/scroll-area.tsx is excluded by !packages/ui/src/components/ui/**
  • packages/ui/src/components/ui/select.tsx is excluded by !packages/ui/src/components/ui/**
  • packages/ui/src/components/ui/separator.tsx is excluded by !packages/ui/src/components/ui/**
  • packages/ui/src/components/ui/shimmer-button.tsx is excluded by !packages/ui/src/components/ui/**
  • packages/ui/src/components/ui/shiny-button.tsx is excluded by !packages/ui/src/components/ui/**
  • packages/ui/src/components/ui/spinner.tsx is excluded by !packages/ui/src/components/ui/**
  • packages/ui/src/components/ui/splash.tsx is excluded by !packages/ui/src/components/ui/**
  • packages/ui/src/components/ui/switch.tsx is excluded by !packages/ui/src/components/ui/**
  • packages/ui/src/components/ui/tabs.tsx is excluded by !packages/ui/src/components/ui/**
  • packages/ui/src/components/ui/text-animate.tsx is excluded by !packages/ui/src/components/ui/**
  • packages/ui/src/components/ui/textarea.tsx is excluded by !packages/ui/src/components/ui/**
  • packages/ui/src/components/ui/tooltip.tsx is excluded by !packages/ui/src/components/ui/**
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (55)
  • apps/desktop/package.json (1 hunks)
  • apps/desktop/src/components/chat/body/index.tsx (1 hunks)
  • apps/desktop/src/components/chat/header.tsx (1 hunks)
  • apps/desktop/src/components/chat/input.tsx (1 hunks)
  • apps/desktop/src/components/chat/interactive.tsx (1 hunks)
  • apps/desktop/src/components/chat/message/normal.tsx (1 hunks)
  • apps/desktop/src/components/chat/message/shared.tsx (1 hunks)
  • apps/desktop/src/components/chat/trigger.tsx (1 hunks)
  • apps/desktop/src/components/main/body/calendars.tsx (3 hunks)
  • apps/desktop/src/components/main/body/contacts/organizations.tsx (1 hunks)
  • apps/desktop/src/components/main/body/contacts/people.tsx (1 hunks)
  • apps/desktop/src/components/main/body/index.tsx (1 hunks)
  • apps/desktop/src/components/main/body/search.tsx (1 hunks)
  • apps/desktop/src/components/main/body/sessions/floating/generate.tsx (1 hunks)
  • apps/desktop/src/components/main/body/sessions/floating/listen.tsx (2 hunks)
  • apps/desktop/src/components/main/body/sessions/note-input/index.tsx (1 hunks)
  • apps/desktop/src/components/main/body/sessions/note-input/raw.tsx (1 hunks)
  • apps/desktop/src/components/main/body/sessions/outer-header/event.tsx (0 hunks)
  • apps/desktop/src/components/main/body/sessions/outer-header/metadata.tsx (3 hunks)
  • apps/desktop/src/components/main/body/sessions/outer-header/participant.tsx (0 hunks)
  • apps/desktop/src/components/main/body/sessions/title-input.tsx (1 hunks)
  • apps/desktop/src/components/main/sidebar/profile/banner.tsx (1 hunks)
  • apps/desktop/src/components/main/sidebar/profile/notification.tsx (1 hunks)
  • apps/desktop/src/components/main/sidebar/search/group.tsx (1 hunks)
  • apps/desktop/src/components/main/sidebar/search/index.tsx (1 hunks)
  • apps/desktop/src/components/main/sidebar/search/item.tsx (1 hunks)
  • apps/desktop/src/components/main/sidebar/timeline/index.tsx (1 hunks)
  • apps/desktop/src/components/main/sidebar/timeline/item.tsx (1 hunks)
  • apps/desktop/src/components/settings/ai/llm/configure.tsx (1 hunks)
  • apps/desktop/src/components/settings/ai/llm/select.tsx (1 hunks)
  • apps/desktop/src/components/settings/ai/shared/model-combobox.tsx (1 hunks)
  • apps/desktop/src/components/settings/ai/stt/configure.tsx (1 hunks)
  • apps/desktop/src/components/settings/ai/stt/select.tsx (1 hunks)
  • apps/desktop/src/contexts/audio-player/timeline.tsx (1 hunks)
  • apps/desktop/src/devtool/index.tsx (1 hunks)
  • apps/desktop/src/devtool/tinytick.tsx (1 hunks)
  • apps/desktop/src/routes/app/onboarding.tsx (3 hunks)
  • apps/desktop/src/routes/app/settings/_layout.tsx (1 hunks)
  • apps/desktop/src/store/tinybase/persisted.ts (1 hunks)
  • apps/desktop/src/utils/timeline.ts (1 hunks)
  • packages/ui/package.json (1 hunks)
  • packages/ui/src/components/block/calendar-structure.tsx (0 hunks)
  • packages/ui/src/components/block/chat-panel-button.tsx (0 hunks)
  • packages/ui/src/components/block/event-chip.tsx (0 hunks)
  • packages/ui/src/components/block/listen-button.tsx (0 hunks)
  • packages/ui/src/components/block/meeting-metadata-chip.tsx (0 hunks)
  • packages/ui/src/components/block/participants-chip.tsx (0 hunks)
  • packages/ui/src/components/block/sound-indicator.tsx (0 hunks)
  • packages/ui/src/components/block/tab-header.tsx (0 hunks)
  • packages/ui/src/components/block/title-input.tsx (0 hunks)
  • packages/ui/src/lib/utils.ts (0 hunks)
  • packages/utils/package.json (1 hunks)
  • packages/utils/src/cn.ts (1 hunks)
  • packages/utils/src/date.ts (1 hunks)
  • packages/utils/src/index.ts (1 hunks)
💤 Files with no reviewable changes (12)
  • apps/desktop/src/components/main/body/sessions/outer-header/event.tsx
  • apps/desktop/src/components/main/body/sessions/outer-header/participant.tsx
  • packages/ui/src/components/block/title-input.tsx
  • packages/ui/src/components/block/chat-panel-button.tsx
  • packages/ui/src/lib/utils.ts
  • packages/ui/src/components/block/sound-indicator.tsx
  • packages/ui/src/components/block/event-chip.tsx
  • packages/ui/src/components/block/tab-header.tsx
  • packages/ui/src/components/block/calendar-structure.tsx
  • packages/ui/src/components/block/listen-button.tsx
  • packages/ui/src/components/block/meeting-metadata-chip.tsx
  • packages/ui/src/components/block/participants-chip.tsx
🧰 Additional context used
🧠 Learnings (4)
📓 Common learnings
Learnt from: CR
PR: fastrepl/hyprnote#0
File: apps/desktop2/.cursor/rules/style.mdc:0-0
Timestamp: 2025-10-12T11:14:50.462Z
Learning: Applies to apps/desktop2/**/*.{tsx,jsx} : When there are many Tailwind classNames with conditional logic, use the cn utility imported as `import { cn } from "hypr/ui/lib/utils"`
📚 Learning: 2025-10-12T11:14:50.462Z
Learnt from: CR
PR: fastrepl/hyprnote#0
File: apps/desktop2/.cursor/rules/style.mdc:0-0
Timestamp: 2025-10-12T11:14:50.462Z
Learning: Applies to apps/desktop2/**/*.{tsx,jsx} : When there are many Tailwind classNames with conditional logic, use the cn utility imported as `import { cn } from "hypr/ui/lib/utils"`

Applied to files:

  • apps/desktop/src/components/main/sidebar/search/group.tsx
  • apps/desktop/src/components/chat/input.tsx
  • packages/utils/src/cn.ts
  • apps/desktop/src/components/chat/interactive.tsx
  • apps/desktop/src/components/chat/body/index.tsx
  • apps/desktop/src/components/main/body/search.tsx
  • apps/desktop/src/components/main/sidebar/profile/notification.tsx
  • apps/desktop/src/components/main/body/sessions/note-input/index.tsx
  • apps/desktop/src/devtool/index.tsx
  • apps/desktop/src/components/main/sidebar/search/index.tsx
  • apps/desktop/src/devtool/tinytick.tsx
  • apps/desktop/src/components/main/body/sessions/floating/generate.tsx
  • apps/desktop/src/routes/app/settings/_layout.tsx
  • apps/desktop/src/components/main/sidebar/profile/banner.tsx
  • apps/desktop/src/components/main/sidebar/timeline/item.tsx
  • apps/desktop/src/components/main/sidebar/timeline/index.tsx
  • apps/desktop/src/components/main/body/index.tsx
  • apps/desktop/src/components/chat/trigger.tsx
  • apps/desktop/src/components/main/sidebar/search/item.tsx
  • apps/desktop/src/components/main/body/sessions/title-input.tsx
  • apps/desktop/src/components/main/body/sessions/note-input/raw.tsx
📚 Learning: 2025-10-12T11:14:50.462Z
Learnt from: CR
PR: fastrepl/hyprnote#0
File: apps/desktop2/.cursor/rules/style.mdc:0-0
Timestamp: 2025-10-12T11:14:50.462Z
Learning: Applies to apps/desktop2/**/*.{tsx,jsx} : Split cn array entries by logical grouping when composing Tailwind classNames

Applied to files:

  • packages/utils/src/cn.ts
  • apps/desktop/src/components/main/body/index.tsx
📚 Learning: 2025-10-12T11:14:50.462Z
Learnt from: CR
PR: fastrepl/hyprnote#0
File: apps/desktop2/.cursor/rules/style.mdc:0-0
Timestamp: 2025-10-12T11:14:50.462Z
Learning: Applies to apps/desktop2/**/*.{tsx,jsx} : Always pass an array to cn when composing Tailwind classNames

Applied to files:

  • packages/utils/src/cn.ts
🧬 Code graph analysis (4)
apps/desktop/src/routes/app/onboarding.tsx (1)
packages/ui/src/components/ui/button.tsx (1)
  • Button (53-53)
apps/desktop/src/components/main/body/sessions/outer-header/metadata.tsx (3)
packages/ui/src/components/icons/linkedin.tsx (1)
  • LinkedInIcon (7-23)
packages/utils/src/cn.ts (1)
  • cn (20-22)
packages/utils/src/date.ts (2)
  • getMeetingDomain (84-91)
  • formatDateRange (64-76)
apps/desktop/src/components/main/body/calendars.tsx (1)
packages/ui/src/components/ui/button.tsx (1)
  • Button (53-53)
apps/desktop/src/components/main/body/sessions/floating/listen.tsx (1)
packages/ui/src/components/ui/dancing-sticks.tsx (1)
  • DancingSticks (49-74)
🔇 Additional comments (44)
apps/desktop/src/components/main/sidebar/profile/notification.tsx (1)

6-6: LGTM! Clean import path migration.

The import path change from @hypr/ui/lib/utils to @hypr/utils aligns with the centralized utilities refactor. The usage of cn throughout the component remains unchanged and follows best practices for managing conditional Tailwind classes.

apps/desktop/src/components/main/sidebar/timeline/item.tsx (1)

5-5: LGTM! Consistent with the centralized utilities migration.

The import path update is clean and consistent with the broader refactoring effort.

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

4-4: LGTM! Import migration looks good.

apps/desktop/src/components/main/body/sessions/note-input/index.tsx (1)

4-4: LGTM! Import path updated correctly.

packages/ui/package.json (1)

19-19: LGTM! Necessary dependency for the centralized utilities.

The addition of @hypr/utils with workspace protocol is correct for the monorepo structure and enables the UI package to consume centralized utilities.

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

14-14: LGTM! Import path migrated correctly.

apps/desktop/src/components/settings/ai/llm/select.tsx (1)

5-5: LGTM! Import path updated correctly.

apps/desktop/src/components/chat/message/normal.tsx (1)

1-1: All verification points confirmed. The import change is safe.

The @hypr/utils package properly exports formatDistanceToNow through export * from "date-fns", ensuring the function signature and behavior match date-fns. The underlying date-fns version (4.1.0) is modern and supports all standard options.

apps/desktop/src/components/settings/ai/stt/select.tsx (1)

5-5: LGTM! Import path updated correctly.

The cn utility import has been successfully migrated to @hypr/utils as part of the broader refactoring to centralize utilities. Usage remains consistent throughout the file.

apps/desktop/src/components/main/body/sessions/note-input/raw.tsx (1)

9-9: LGTM! Import path updated correctly.

The cn utility import has been successfully migrated to @hypr/utils, consistent with the project-wide refactoring effort.

apps/desktop/src/components/settings/ai/shared/model-combobox.tsx (1)

15-15: LGTM! Import path updated correctly.

The cn utility import has been successfully migrated to @hypr/utils. The function is used extensively throughout the file for managing conditional Tailwind classes, and all usages remain correct.

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

5-5: LGTM! Import path updated correctly.

The cn utility import has been successfully migrated to @hypr/utils, aligning with the broader refactoring to centralize utilities.

apps/desktop/src/components/chat/interactive.tsx (1)

4-4: LGTM! Import path updated correctly.

The cn utility import has been successfully migrated to @hypr/utils, consistent with the project-wide utility consolidation effort.

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

4-4: LGTM! Import path updated correctly.

The cn utility import has been successfully migrated to @hypr/utils. Usage throughout the file remains consistent and correct.

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

5-5: LGTM! Import path updated correctly.

The cn utility import has been successfully migrated to @hypr/utils. This file makes extensive use of cn for conditional Tailwind classes, which aligns with the project's coding guidelines.

apps/desktop/src/devtool/tinytick.tsx (1)

4-4: LGTM! Import path updated for centralized utilities.

The import path change from @hypr/ui/lib/utils to @hypr/utils aligns with the project-wide refactoring to consolidate utilities into a centralized package.

apps/desktop/src/components/chat/message/shared.tsx (1)

4-4: LGTM! Import path updated consistently.

The cn utility import has been updated to use the centralized @hypr/utils package.

apps/desktop/src/components/chat/body/index.tsx (1)

4-4: LGTM! Consistent with the utilities refactoring.

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

4-4: LGTM! Import path updated.

apps/desktop/src/components/main/body/sessions/title-input.tsx (1)

1-1: LGTM! Consistent import path update.

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

2-2: LGTM! Import path updated.

apps/desktop/src/store/tinybase/persisted.ts (1)

1-1: The review comment is based on an incorrect understanding of the change.

The import change from "date-fns" to "@hypr/utils" is a simple path refactoring, not a replacement with a custom implementation. The @hypr/utils package re-exports all date-fns functions directly via export * from "date-fns", making the imported format function identical to date-fns's format function.

Key findings:

  • @hypr/utils/src/date.ts line 9 re-exports all date-fns functions unchanged
  • The formatDate function defined in that file is a separate custom utility, not the imported format
  • The format function called at lines 441 and 465 with "yyyy-MM-dd" tokens is the exact same date-fns function
  • API compatibility, edge case handling, and performance are guaranteed because it's the identical function

This is actually a beneficial refactoring—centralizing date utilities through @hypr/utils provides a single point of control for date library imports.

Likely an incorrect or invalid review comment.

apps/desktop/src/contexts/audio-player/timeline.tsx (1)

3-3: LGTM! Clean import path migration.

The import path update for cn from the UI package to the centralized utils package is consistent with the PR's consolidation objective.

apps/desktop/src/components/chat/input.tsx (1)

4-4: LGTM! Import path updated correctly.

The cn utility import has been successfully migrated to the centralized utils package.

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

2-2: LGTM! Consistent with the codebase-wide migration.

The import path update aligns with the centralized utilities approach.

apps/desktop/src/devtool/index.tsx (1)

6-6: LGTM! Import migration applied correctly.

The devtool component now uses the centralized cn utility.

apps/desktop/src/components/settings/ai/stt/configure.tsx (1)

13-13: LGTM! Settings component updated correctly.

The import path migration is consistent with the broader refactoring effort.

apps/desktop/package.json (1)

35-35: LGTM! Required dependency added.

The new @hypr/utils workspace dependency enables the centralized utilities pattern used throughout the application.

apps/desktop/src/components/chat/header.tsx (2)

7-7: LGTM! Import path updated correctly.

The cn utility import follows the same migration pattern as other files in this PR.


1-1: Import change is safe—API compatibility verified.

The @hypr/utils package properly re-exports formatDistanceToNow from date-fns via export * from "date-fns" in packages/utils/src/date.ts, maintaining full API compatibility with the usage at line 172.

apps/desktop/src/routes/app/onboarding.tsx (3)

15-15: LGTM! Import path migrated successfully.

The cn utility import has been updated to use the centralized utils package.


192-194: Second Button replacement looks consistent.

This replacement follows the same pattern as the first one on lines 79-84, maintaining consistency throughout the onboarding flow.


79-84: Migration from PushableButton to Button is complete and correct.

Verification confirms:

  • No remaining PushableButton references in the codebase
  • Button component properly imported and used in onboarding.tsx with correct props (onClick, className)
  • ButtonProps extends React.ButtonHTMLAttributes, fully supporting onClick, className, and standard button behavior
  • Visual styling maintained through CVA-based variants and className composition
  • All functionality preserved in the migration
apps/desktop/src/components/main/body/contacts/people.tsx (1)

3-3: Import path updated correctly.

The cn utility import has been successfully migrated to the centralized @hypr/utils package, aligning with the PR's consolidation objective.

apps/desktop/src/components/chat/trigger.tsx (1)

1-1: Import path migrated correctly.

The transition to @hypr/utils is consistent with the project-wide utility consolidation effort.

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

3-3: Utility import centralization complete.

The import path change to @hypr/utils is correct and consistent with the refactoring strategy.

packages/utils/package.json (1)

16-16: Dependencies added for cn utility support.

The clsx (v2.1.1) and tailwind-merge (v2.6.0) dependencies are the correct libraries and versions for the centralized cn utility implementation. Based on learnings.

Also applies to: 21-21

apps/desktop/src/utils/timeline.ts (1)

1-1: Date utilities consolidated effectively.

The import consolidation to @hypr/utils is correct, and all required date-fns functions are properly imported and used throughout the file.

packages/utils/src/index.ts (1)

1-2: Public API surface expanded correctly.

The re-exports for cn and date utilities properly expose the new centralized utilities alongside existing fetch exports.

packages/utils/src/cn.ts (1)

1-22: Well-implemented utility with excellent documentation.

The cn utility correctly combines clsx for conditional class composition and tailwind-merge for conflict resolution. The comprehensive JSDoc documentation with practical examples will help developers use this utility effectively.

packages/utils/src/date.ts (2)

64-76: Date range formatting logic is sound.

The intelligent same-day detection using isSameDay ensures appropriate formatting for both single-day and multi-day ranges.


84-91: URL parsing is defensive and correct.

The try-catch wrapper appropriately handles malformed URLs by falling back to the original string.

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

1-1: Replace clsx imports with cn utility from @hypr/utils to standardize class merging.

Nine files incorrectly import clsx directly from the "clsx" package and should use cn instead. The cn utility (defined in packages/utils/src/cn.ts) wraps clsx with twMerge for proper Tailwind class conflict resolution. Update the following files:

  • apps/desktop/src/routes/app/auth.tsx:5
  • apps/desktop/src/components/main/body/calendars.tsx:2
  • apps/desktop/src/components/main/body/shared.tsx:4
  • apps/desktop/src/components/main/sidebar/index.tsx:3
  • apps/desktop/src/components/main/sidebar/timeline/index.tsx:4
  • apps/desktop/src/components/main/sidebar/profile/notification.tsx:3
  • apps/desktop/src/components/main/sidebar/profile/shared.tsx:1
  • apps/desktop/src/components/main/sidebar/profile/index.tsx:3
  • apps/desktop/src/components/main/sidebar/profile/ota.tsx:7

Replace import { clsx } from "clsx" with import { cn } from "@hypr/utils" and update all clsx(...) calls to cn(...).

⛔ Skipped due to learnings
Learnt from: CR
PR: fastrepl/hyprnote#0
File: apps/desktop2/.cursor/rules/style.mdc:0-0
Timestamp: 2025-10-12T11:14:50.462Z
Learning: Applies to apps/desktop2/**/*.{tsx,jsx} : When there are many Tailwind classNames with conditional logic, use the cn utility imported as `import { cn } from "hypr/ui/lib/utils"`
Learnt from: CR
PR: fastrepl/hyprnote#0
File: apps/desktop2/.cursor/rules/style.mdc:0-0
Timestamp: 2025-10-12T11:14:50.462Z
Learning: Applies to apps/desktop2/**/*.{tsx,jsx} : Split cn array entries by logical grouping when composing Tailwind classNames
Learnt from: CR
PR: fastrepl/hyprnote#0
File: apps/desktop2/.cursor/rules/style.mdc:0-0
Timestamp: 2025-10-12T11:14:50.462Z
Learning: Applies to apps/desktop2/**/*.{tsx,jsx} : Always pass an array to cn when composing Tailwind classNames
apps/desktop/src/components/main/body/sessions/floating/listen.tsx (1)

3-5: LGTM - Import changes support the local SoundIndicator implementation.

The addition of useEffect and the change from importing SoundIndicator to DancingSticks correctly support the new local implementation that replaces the removed UI package component.

@@ -1,9 +1,9 @@
import { addMonths, eachDayOfInterval, endOfMonth, format, getDay, isSameMonth, startOfMonth } from "@hypr/utils";
import { clsx } from "clsx";
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Use cn from @hypr/utils and remove incorrect clsx named import.

Named import clsx breaks (it’s a default export), and our standard is cn for Tailwind merging. Replace usages accordingly. Based on learnings.

Apply:

-import { clsx } from "clsx";
+// use cn (clsx + tailwind-merge) from utils
+// ref: packages/utils/src/cn.ts

And merge into the first import:

-import { addMonths, eachDayOfInterval, endOfMonth, format, getDay, isSameMonth, startOfMonth } from "@hypr/utils";
+import { addMonths, eachDayOfInterval, endOfMonth, format, getDay, isSameMonth, startOfMonth, cn } from "@hypr/utils";

Replace usages:

-      className={clsx([
+      className={cn(
         "h-32 relative flex flex-col border-b border-neutral-200",
-        isWeekend ? "bg-neutral-50" : "bg-white",
-      ])}
+        isWeekend ? "bg-neutral-50" : "bg-white",
+      )}
-        <div className={clsx("flex items-end gap-1", isToday && "items-center")}>
+        <div className={cn("flex items-end gap-1", isToday && "items-center")}>
-          <div
-            className={clsx(
+          <div
+            className={cn(
               isToday && "bg-red-500 rounded-full w-6 h-6 flex items-center justify-center",
-            )}
+            )}
           >
             <span
-              className={clsx(
+              className={cn(
                 isToday
                   ? "text-white font-medium"
                   : !isCurrentMonth
                   ? "text-neutral-400"
                   : isWeekend
                   ? "text-neutral-500"
                   : "text-neutral-700",
-              )}
+              )}
             >

Also applies to: 163-167, 169-177, 176-184

🤖 Prompt for AI Agents
In apps/desktop/src/components/main/body/calendars.tsx around line 2 (and also
applicable to usages at lines ~163-167, 169-177, 176-184), the file incorrectly
imports a named `clsx` and uses it; replace this with the project's `cn` helper
from `@hypr/utils`. Remove the `import { clsx } from "clsx";` line, merge an
import for `cn` into the first import statement at the top of the file (e.g. add
`cn` to that import), and then change all calls to `clsx(...)` to `cn(...)` so
Tailwind class merging uses the standard helper.

Comment on lines +102 to +110
<div className="grid grid-cols-7 divide-x divide-neutral-200 h-[calc(100%-48px)] grid-rows-6 gap-0">
{Array.from({ length: startDayOfWeek }).map((_, i) => (
<div key={`empty-${i}`} className="border-b border-neutral-200" />
))}
{days.map((day) => (
<TabContentCalendarDay key={day} day={day} isCurrentMonth={isSameMonth(new Date(day), tab.month)} />
))}
</div>
</div>
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Fill trailing empty cells to complete the 6×7 calendar grid.

Only leading blanks are rendered; trailing cells are missing, causing broken borders/layout in shorter months.

Apply:

@@
-  const monthLabel = format(tab.month, "MMMM yyyy");
+  const monthLabel = format(tab.month, "MMMM yyyy");
+  // ensure grid is always 6x7
+  const TOTAL_CELLS = 42;
+  const trailingEmptyCount = Math.max(TOTAL_CELLS - (startDayOfWeek + days.length), 0);
@@
-          <div className="grid grid-cols-7 divide-x divide-neutral-200 h-[calc(100%-48px)] grid-rows-6 gap-0">
+          <div className="grid grid-cols-7 divide-x divide-neutral-200 h-[calc(100%-48px)] grid-rows-6 gap-0">
             {Array.from({ length: startDayOfWeek }).map((_, i) => (
               <div key={`empty-${i}`} className="border-b border-neutral-200" />
             ))}
             {days.map((day) => (
               <TabContentCalendarDay key={day} day={day} isCurrentMonth={isSameMonth(new Date(day), tab.month)} />
             ))}
+            {Array.from({ length: trailingEmptyCount }).map((_, i) => (
+              <div key={`empty-trailing-${i}`} className="border-b border-neutral-200" />
+            ))}
           </div>

Also applies to: 46-47

🤖 Prompt for AI Agents
In apps/desktop/src/components/main/body/calendars.tsx around lines 102-110
(also applies to lines 46-47), the calendar only renders leading blank cells
which leaves trailing cells out and breaks the 6x7 grid; after mapping leading
blanks and the month days, compute remaining = 42 - startDayOfWeek - days.length
and render that many trailing empty cells (use the same element structure,
classes and unique keys as the leading blanks) so the total rendered cells
always equals 42 and borders/layout stay consistent.

import { useState } from "react";

import { cn } from "@hypr/ui/lib/utils";
import { cn } from "@hypr/utils";
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

LGTM! Import path updated consistently.

The refactoring to centralize utilities appears complete across all reviewed files. Consider running a codebase-wide verification to ensure no imports were missed.

Run the following script to verify all import paths have been updated consistently:


🏁 Script executed:

#!/bin/bash
# Verify all utility imports have been migrated to @hypr/utils

echo "=== Checking for any remaining old cn imports ==="
rg -n "from ['\"]@hypr/ui/lib/utils['\"]" -g '*.{ts,tsx}' -g '!node_modules'

echo -e "\n=== Verifying new @hypr/utils imports ==="
rg -n "from ['\"]@hypr/utils['\"]" -g '*.{ts,tsx}' -g '!node_modules' | head -20

echo -e "\n=== Checking @hypr/utils package exists and exports cn ==="
cat packages/utils/src/index.ts 2>/dev/null || echo "Could not find packages/utils/src/index.ts"
cat packages/utils/src/cn.ts 2>/dev/null || echo "Could not find packages/utils/src/cn.ts"

Length of output: 2852


Incomplete import migration detected. Two files still use the old path.

The refactoring is incomplete. While most imports have been updated to @hypr/utils, two files still reference the old @hypr/ui/lib/utils path:

  • packages/ui/src/components/ui/progressive-blur.tsx:2
  • packages/ui/src/components/ui/kbd.tsx:1

These must be updated to import from @hypr/utils to complete the consolidation.

🤖 Prompt for AI Agents
In apps/desktop/src/components/main/body/sessions/floating/generate.tsx around
line 4 the project is still referencing the old util path; update any imports
that use "@hypr/ui/lib/utils" to the consolidated package "@hypr/utils".
Specifically, change the import statements in the two remaining files
packages/ui/src/components/ui/progressive-blur.tsx (line 2) and
packages/ui/src/components/ui/kbd.tsx (line 1) to import from "@hypr/utils",
then run the build/tests to verify no other files reference the old path.

Comment on lines +91 to +103
function SoundIndicator({ value, color }: { value: number | Array<number>; color?: string }) {
const [amplitude, setAmplitude] = useState(0);

const u16max = 65535;
useEffect(() => {
const sample = Array.isArray(value)
? (value.reduce((sum, v) => sum + v, 0) / value.length) / u16max
: value / u16max;
setAmplitude(Math.min(sample, 1));
}, [value]);

return <DancingSticks amplitude={amplitude} color={color} size="long" />;
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

Handle empty array edge case and verify value range assumptions.

The amplitude calculation has a potential edge case issue:

  1. Empty array bug (Line 96-98): If value is an empty array, the calculation 0 / value.length produces NaN, which will propagate through to DancingSticks. Consider adding a guard:
 const u16max = 65535;
 useEffect(() => {
+  if (Array.isArray(value) && value.length === 0) {
+    setAmplitude(0);
+    return;
+  }
   const sample = Array.isArray(value)
     ? (value.reduce((sum, v) => sum + v, 0) / value.length) / u16max
     : value / u16max;
   setAmplitude(Math.min(sample, 1));
 }, [value]);
  1. Verify value range: The use of u16max = 65535 assumes unsigned 16-bit values. If amplitude.mic or amplitude.speaker can be negative (signed audio samples), the normalization will produce negative results. Please verify that these values are guaranteed to be in the 0-65535 range.

🏁 Script executed:

#!/bin/bash
# Search for amplitude value assignments in listener context to verify range
rg -nP --type=ts --type=tsx -C5 'amplitude\s*[=:].*\{.*mic.*speaker.*\}' 

Length of output: 27


🏁 Script executed:

#!/bin/bash
# Search for where amplitude object with mic/speaker properties is created/assigned
rg -nP -C8 'amplitude\s*[=:]\s*\{[^}]*mic' -g '*.ts' -g '*.tsx'

Length of output: 2245


🏁 Script executed:

#!/bin/bash
# Search for SoundIndicator usage and the amplitude values being passed
rg -nP -C5 'SoundIndicator' -g '*.ts' -g '*.tsx'

Length of output: 2205


🏁 Script executed:

#!/bin/bash
# Search for audio capture or amplitude processing that creates mic/speaker values
rg -nP -C5 '\.mic|\.speaker' -g '*.ts' -g '*.tsx' | head -100

Length of output: 8082


🏁 Script executed:

#!/bin/bash
# Check if there's any validation on payload values or plugin listener type definitions
rg -nP -C10 'audioAmplitude' -g '*.ts' -g '*.tsx'

Length of output: 3987


🏁 Script executed:

#!/bin/bash
# Search for @hypr/plugin-listener to understand the types
rg -nP '@hypr/plugin-listener' -g '*.ts' -g '*.tsx' -g '*.json'

Length of output: 490


🏁 Script executed:

#!/bin/bash
# Look for any normalization or validation of audio amplitude values
rg -nP -C5 'payload\.(mic|speaker)' -g '*.ts' -g '*.tsx'

Length of output: 966


Remove incorrect empty array concern; fix normalization to clamp negative values.

The empty array edge case is not applicable—SoundIndicator always receives exactly two elements [amplitude.mic, amplitude.speaker].

However, the normalization logic has a real issue: Math.min(sample, 1) only clamps the upper bound. If payload.mic or payload.speaker are negative (which is possible with signed audio samples), negative amplitudes will be passed to DancingSticks. The fix should ensure the 0-1 range:

 const u16max = 65535;
 useEffect(() => {
   const sample = Array.isArray(value)
     ? (value.reduce((sum, v) => sum + v, 0) / value.length) / u16max
     : value / u16max;
-  setAmplitude(Math.min(sample, 1));
+  setAmplitude(Math.max(0, Math.min(sample, 1)));
 }, [value]);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
function SoundIndicator({ value, color }: { value: number | Array<number>; color?: string }) {
const [amplitude, setAmplitude] = useState(0);
const u16max = 65535;
useEffect(() => {
const sample = Array.isArray(value)
? (value.reduce((sum, v) => sum + v, 0) / value.length) / u16max
: value / u16max;
setAmplitude(Math.min(sample, 1));
}, [value]);
return <DancingSticks amplitude={amplitude} color={color} size="long" />;
}
function SoundIndicator({ value, color }: { value: number | Array<number>; color?: string }) {
const [amplitude, setAmplitude] = useState(0);
const u16max = 65535;
useEffect(() => {
const sample = Array.isArray(value)
? (value.reduce((sum, v) => sum + v, 0) / value.length) / u16max
: value / u16max;
setAmplitude(Math.max(0, Math.min(sample, 1)));
}, [value]);
return <DancingSticks amplitude={amplitude} color={color} size="long" />;
}
🤖 Prompt for AI Agents
In apps/desktop/src/components/main/body/sessions/floating/listen.tsx around
lines 91 to 103, remove the unnecessary empty-array handling (SoundIndicator
always receives exactly two elements) and normalize/clamp amplitudes into the
0–1 range: compute the sample as either the average of the two-array values or
value as-is, divide by u16max, then ensure you clamp both lower and upper bounds
(e.g., use a clamp like Math.max(0, Math.min(sample, 1))) or take absolute value
before normalization if signed samples should be treated as magnitude; finally
call setAmplitude with the clamped 0–1 value.

Comment on lines +139 to +160
const handleKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
if (!searchQuery.trim() || searchResults.length === 0) {
return;
}

if (e.key === "ArrowDown") {
e.preventDefault();
setSelectedIndex((prev) => (prev < searchResults.length - 1 ? prev + 1 : prev));
} else if (e.key === "ArrowUp") {
e.preventDefault();
setSelectedIndex((prev) => (prev > 0 ? prev - 1 : 0));
} else if (e.key === "Enter") {
e.preventDefault();
if (selectedIndex >= 0 && selectedIndex < searchResults.length) {
handleSelectParticipant(searchResults[selectedIndex].id);
}
} else if (e.key === "Escape") {
e.preventDefault();
setIsFocused(false);
onSearchChange?.("");
}
};
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Always handle Escape; don’t early-return before key processing.

Escape should clear/close even when there are no results.

-  const handleKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
-    if (!searchQuery.trim() || searchResults.length === 0) {
-      return;
-    }
-
-    if (e.key === "ArrowDown") {
+  const handleKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
+    if (e.key === "Escape") {
+      e.preventDefault();
+      setIsFocused(false);
+      onSearchChange?.("");
+      return;
+    }
+    if (!searchQuery.trim() || searchResults.length === 0) {
+      return;
+    }
+    if (e.key === "ArrowDown") {
       e.preventDefault();
       setSelectedIndex((prev) => (prev < searchResults.length - 1 ? prev + 1 : prev));
     } else if (e.key === "ArrowUp") {
       e.preventDefault();
       setSelectedIndex((prev) => (prev > 0 ? prev - 1 : 0));
     } else if (e.key === "Enter") {
       e.preventDefault();
       if (selectedIndex >= 0 && selectedIndex < searchResults.length) {
         handleSelectParticipant(searchResults[selectedIndex].id);
       }
-    } else if (e.key === "Escape") {
-      e.preventDefault();
-      setIsFocused(false);
-      onSearchChange?.("");
     }
   };
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const handleKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
if (!searchQuery.trim() || searchResults.length === 0) {
return;
}
if (e.key === "ArrowDown") {
e.preventDefault();
setSelectedIndex((prev) => (prev < searchResults.length - 1 ? prev + 1 : prev));
} else if (e.key === "ArrowUp") {
e.preventDefault();
setSelectedIndex((prev) => (prev > 0 ? prev - 1 : 0));
} else if (e.key === "Enter") {
e.preventDefault();
if (selectedIndex >= 0 && selectedIndex < searchResults.length) {
handleSelectParticipant(searchResults[selectedIndex].id);
}
} else if (e.key === "Escape") {
e.preventDefault();
setIsFocused(false);
onSearchChange?.("");
}
};
const handleKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
if (e.key === "Escape") {
e.preventDefault();
setIsFocused(false);
onSearchChange?.("");
return;
}
if (!searchQuery.trim() || searchResults.length === 0) {
return;
}
if (e.key === "ArrowDown") {
e.preventDefault();
setSelectedIndex((prev) => (prev < searchResults.length - 1 ? prev + 1 : prev));
} else if (e.key === "ArrowUp") {
e.preventDefault();
setSelectedIndex((prev) => (prev > 0 ? prev - 1 : 0));
} else if (e.key === "Enter") {
e.preventDefault();
if (selectedIndex >= 0 && selectedIndex < searchResults.length) {
handleSelectParticipant(searchResults[selectedIndex].id);
}
}
};
🤖 Prompt for AI Agents
In apps/desktop/src/components/main/body/sessions/outer-header/metadata.tsx
around lines 139 to 160, the current early return when searchQuery is empty or
searchResults is empty prevents the Escape key from being handled; move the
Escape handling above that early return (or remove the early return and guard
only key-specific branches) so that when e.key === "Escape" you always call
e.preventDefault(), setIsFocused(false), and call onSearchChange?.("")
regardless of search state; keep the existing behavior for ArrowDown, ArrowUp,
and Enter but only run those when there are results.


import { Accordion, AccordionContent, AccordionItem, AccordionTrigger } from "@hypr/ui/components/ui/accordion";
import { cn } from "@hypr/ui/lib/utils";
import { cn } from "@hypr/utils";
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

LGTM! Import path updated correctly.

The cn utility import has been successfully migrated to @hypr/utils. This change is part of the broader effort to consolidate utilities into a centralized package.

Since this is a widespread change across many files, please verify that all cn imports throughout the codebase have been updated consistently:


🏁 Script executed:

#!/bin/bash
# Description: Verify all cn imports are using the new @hypr/utils path
# and identify any remaining old imports

echo "=== Checking for old cn imports from @hypr/ui/lib/utils ==="
rg -n 'from ["\x27]@hypr/ui/lib/utils["\x27]' --type tsx --type ts -g '!node_modules' -g '!dist' -g '!build'

echo ""
echo "=== Checking for new cn imports from @hypr/utils ==="
rg -n 'from ["\x27]@hypr/utils["\x27]' --type tsx --type ts -g '!node_modules' -g '!dist' -g '!build' -C 0 | head -20

echo ""
echo "=== Verifying @hypr/utils package exists ==="
fd -t f 'package.json' packages/utils

Length of output: 243


🏁 Script executed:

#!/bin/bash
# Description: Verify cn import migration with glob patterns

echo "=== Checking for old cn imports from @hypr/ui/lib/utils ==="
rg -n 'from ["\x27]@hypr/ui/lib/utils["\x27]' -g '*.ts' -g '*.tsx' -g '!node_modules' -g '!dist' -g '!build'

echo ""
echo "=== Checking for new cn imports from @hypr/utils ==="
rg -n 'from ["\x27]@hypr/utils["\x27]' -g '*.ts' -g '*.tsx' -g '!node_modules' -g '!dist' -g '!build'

echo ""
echo "=== Verify specific file has correct import ==="
grep -n 'import.*cn.*from.*@hypr/utils' apps/desktop/src/components/settings/ai/llm/configure.tsx

echo ""
echo "=== Verify cn is actually used in the file ==="
grep -n '\bcn(' apps/desktop/src/components/settings/ai/llm/configure.tsx | head -5

Length of output: 6557


Migration incomplete: Two files still using old import path.

The file apps/desktop/src/components/settings/ai/llm/configure.tsx has been correctly updated to import cn from @hypr/utils. However, the broader codebase migration is incomplete. Two files still import from the old path and must be updated:

  • packages/ui/src/components/ui/progressive-blur.tsx:2
  • packages/ui/src/components/ui/kbd.tsx:1

Update these files to import cn from @hypr/utils to complete the refactoring effort.

🤖 Prompt for AI Agents
packages/ui/src/components/ui/progressive-blur.tsx line 2 and
packages/ui/src/components/ui/kbd.tsx line 1: both files still import cn from
the old path; update their import statements to import cn from "@hypr/utils"
instead of the previous module path, and ensure any eslint/tsconfig aliases are
satisfied so the new path resolves correctly.

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