Skip to content

Conversation

@ComputelessComputer
Copy link
Collaborator

  • Added hover card component
  • Moved components to a separate folder and added teams section
  • Improved modal overflow prevention
  • Implemented button group
  • Added view invitee functionality
  • Minimized context menu
  • Adjusted modal width and padding
  • Updated color scheme (gray to neutral)

@coderabbitai
Copy link

coderabbitai bot commented Oct 20, 2025

📝 Walkthrough

Walkthrough

Wide-ranging visual theme migration from gray→neutral, UI restructures (Participant HoverCard with meeting-link dropdown/openUrl, profile panel animated refactor, calendar replaced by 6×7 grid with calendar filtering), added Teams meeting type, removed TipTap Document extension, and updated utils alias.

Changes

Cohort / File(s) Change Summary
Chat UI
apps/desktop/src/components/chat/input.tsx, apps/desktop/src/components/chat/message/normal.tsx, apps/desktop/src/components/chat/message/shared.tsx, apps/desktop/src/components/chat/message/tool/search.tsx
Added defaultValue="auto" to a Select; swapped many Tailwind gray tokens to neutral variants in message and tool components (styling-only).
Sessions — Metadata / Meeting Flow
apps/desktop/src/components/main/body/sessions/outer-header/metadata.tsx, .../other.tsx, .../share.tsx
Replaced inline ParticipantChip with HoverCard (attended flag, hover panel, remove action); meeting-link DropdownMenu with Open (uses openUrl) and Copy; Popover/HoverCard sizing and participant keyboard removal; added cursor-pointer on menu items.
Sessions — Listening / Note UI
apps/desktop/src/components/main/body/sessions/floating/listen.tsx, apps/desktop/src/components/main/body/sessions/index.tsx, apps/desktop/src/components/main/body/sessions/note-input/raw.tsx
Added teams remote meeting type and assets; reworked TabContentNote wrapper and FloatingActionButton placement; minor color/layout tweaks in note input and DuringMeetingButton.
Calendar overhaul
apps/desktop/src/components/main/body/calendars.tsx, apps/desktop/src/devtool/seed/shared.ts
Replaced single-month calendar with a 6×7 grid, added sidebar calendar filtering (selectedCalendars), new TabContentCalendarDay and CalendarCheckboxRow, centralized formatting via @hypr/utils, and adjusted seed calendar name templates.
Contacts — Panels & Search
apps/desktop/src/components/main/body/contacts/index.tsx, .../organization-details.tsx, .../organizations.tsx, .../people.tsx, .../shared.tsx
Replaced static layout with ResizablePanelGroup (Organizations, People, Details); added OrganizationDetailsColumn; people/orgs gain search/filtering; ColumnHeader API expanded (searchValue/onSearchChange); SortOption adds reverse-alphabetical; some component signatures updated.
Sidebar / Profile / Timeline / Search items
apps/desktop/src/components/main/sidebar/index.tsx, apps/desktop/src/components/main/sidebar/profile/*, apps/desktop/src/components/main/sidebar/search/*, apps/desktop/src/components/main/sidebar/timeline/*, apps/desktop/src/components/main/sidebar/timeline/item.tsx, apps/desktop/src/components/main/sidebar/search/item.tsx
ProfileSection stacking/z-index change and profile panel refactor to absolute animated panel with AnimatePresence; banner animation tweak; broad neutral token migration; simplified context menus and updated z-index/hover states.
Settings / Onboarding / Auth / Plugins / Web
apps/desktop/src/components/settings/**, apps/desktop/src/routes/app/onboarding.tsx, apps/desktop/src/routes/app/auth.tsx, apps/web/src/routes/callback/auth.tsx, plugins/auth/templates/callback.jinja, plugins/listener/assets/viewer.html
Systematic gray→neutral Tailwind token updates across settings, onboarding, auth, web callback, templates, and listener viewer; small layout/spacing tweaks; no logic changes.
Editor / Packages / Utils
packages/tiptap/src/editor/index.tsx, packages/ui/components.json, packages/ui/package.json, packages/utils/src/cn.ts
Removed TipTap Document extension from editor setup; changed alias "utils"@hypr/utils in components.json; added dependency @radix-ui/react-hover-card; updated example in cn.ts.
UI tokens & misc
many files under apps/desktop/src/components/**, apps/desktop/src/routes/**, apps/desktop/src/contexts/**, apps/desktop/src/devtool/**, plugins/listener/assets/viewer.html
Broad, low-level theme migration from gray→neutral across numerous components, plus minor layout/spacing adjustments and small UI tweaks (button sizes, z-index, icon swaps).

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant ParticipantChip
    participant HoverCard
    participant DropdownMenu
    participant App
    Note over ParticipantChip,HoverCard: Participant hover & meeting actions
    User->>ParticipantChip: hover
    ParticipantChip->>HoverCard: show HoverCard (details)
    User->>HoverCard: click "Remove"
    HoverCard->>App: removeParticipant(id)
    App->>User: participant removed UI update
    User->>DropdownMenu: open -> select "Open link"
    DropdownMenu->>App: requestOpenLink(url)
    App->>App: openUrl(url) (tauri opener)
    App->>User: external URL opened
Loading
sequenceDiagram
    participant User
    participant CalendarUI
    participant Sidebar
    participant App
    Note over CalendarUI,Sidebar: Calendar grid (6×7) with filtering
    User->>Sidebar: toggle calendar checkbox (calendarId)
    Sidebar->>App: update selectedCalendars
    App->>CalendarUI: re-render days with selectedCalendars
    CalendarUI->>User: updated day cells (events filtered)
    User->>CalendarUI: click day -> open details/popover
    CalendarUI->>App: request day details
    App->>User: show events/sessions for that day
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

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 0.00% 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 PR title "Add Hover Card and Button Group Components, Refactor Structure, and Improve UI" is partially related to the changeset. It accurately reflects several significant changes present in the PR: hover card component additions (evident in metadata.tsx), button group implementation, structural refactoring (particularly in calendar layout, contacts panel layout with ResizablePanelGroup, and metadata restructuring), and UI improvements. However, the title does not capture the most pervasive change in this PR—a comprehensive color scheme update from gray to neutral tokens affecting approximately 40+ files throughout the codebase. Additionally, significant features like Teams support integration and the major calendar/contacts refactoring are not reflected in the title.
Description Check ✅ Passed The PR description is related to the changeset. Each listed point corresponds to actual changes present in the PR: hover card components are added (metadata.tsx), Teams section integration is present (listen.tsx), modal overflow prevention is implemented (metadata.tsx and share.tsx with max-height and scroll behavior), button group is implemented, context menu is refactored with minimized items (other.tsx), modal width and padding are adjusted across multiple files, and the gray-to-neutral color scheme update is comprehensively applied. While the description does not exhaustively cover all changes (such as the calendar grid refactoring or contacts panel restructuring), it 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: 3

Caution

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

⚠️ Outside diff range comments (2)
apps/desktop/src/components/settings/ai/stt/select.tsx (1)

5-5: Fix import path for cn utility to match coding guidelines.

Per coding guidelines, the cn utility in apps/desktop/**/*.{tsx,jsx} files should be imported from @hypr/ui/lib/utils, not @hypr/utils.

-import { cn } from "@hypr/utils";
+import { cn } from "@hypr/ui/lib/utils";
apps/desktop/src/components/main/body/sessions/floating/listen.tsx (1)

148-158: Critical: Hardcoded return value breaks meeting type detection.

The useRemoteMeeting function has two critical issues:

  1. Hardcoded return: The function always returns { type: "google-meet", url: null } regardless of the session's actual remote meeting type. This breaks the Teams button added in this PR and likely breaks Zoom/Webex functionality as well.

  2. Debug console.log: Line 150 contains a console.log(note) that should be removed before merging.

The function needs proper implementation to parse the note data and detect the actual meeting platform (Teams, Zoom, Google Meet, or Webex) from the session metadata.

Is this stub code that's planned for a follow-up PR, or should it be implemented in this PR? If this is intentional stub code, please add a TODO comment explaining the implementation plan.

🧹 Nitpick comments (12)
apps/desktop/src/components/settings/ai/stt/select.tsx (1)

33-40: Consider structured logging instead of direct console.log.

Error logging via console.log is somewhat bare; consider using a structured logger or error tracking system if available in the codebase.

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

3-3: Update import path to match coding guidelines.

Per the coding guidelines for apps/desktop/**/*.{tsx,jsx}, the cn utility should be imported from @hypr/ui/lib/utils, but this file imports from @hypr/utils. Verify whether this should be updated for consistency.

-import { cn } from "@hypr/utils";
+import { cn } from "@hypr/ui/lib/utils";
apps/desktop/src/components/settings/templates.tsx (1)

43-43: Prefer semantic tokens over fixed neutrals where possible.

Consider text-muted-foreground for descriptions and hover:bg-muted (or design-token equivalents) for hover states to improve theme portability and dark mode consistency.

Also applies to: 75-75, 79-79

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

86-88: Use ring-based focus styles for accessibility consistency.

Replace focus:border-black with design-system focus tokens (e.g., focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2) to improve visibility and match UI components.

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

48-48: Make helper text actionable (and accessible).

This looks like a call-to-action. Consider rendering it as an anchor or button with a clear target/handler and accessible name instead of a styled paragraph.


37-37: Prop type cleanup.

SearchNoResults props type includes setQuery but it’s unused in the component. Drop it from the type to avoid confusion.

-function SearchNoResults({ query }: { query: string; setQuery: (query: string) => void }) {
+function SearchNoResults({ query }: { query: string }) {
apps/desktop/src/routes/app/settings/_layout.tsx (1)

119-119: Consider semantic design tokens for theming.

Where feasible, prefer bg-muted, text-muted-foreground, and state variants to ease dark-mode/theming tweaks across the app.

Also applies to: 136-140

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

116-119: Avoid slicing raw HTML before sanitization; rely on CSS clamp.

slice(0, 200) can truncate inside tags and degrade highlights. You already use line-clamp-2; remove the slice and sanitize the full snippet to keep markup intact.

-  const sanitizedContent = useMemo(
-    () => DOMPurify.sanitize(result.contentHighlighted.slice(0, 200), { ALLOWED_TAGS: ["mark"], ALLOWED_ATTR: [] }),
-    [result.contentHighlighted],
-  );
+  const sanitizedContent = useMemo(
+    () => DOMPurify.sanitize(result.contentHighlighted, { ALLOWED_TAGS: ["mark"], ALLOWED_ATTR: [] }),
+    [result.contentHighlighted],
+  );

Optionally enable Trusted Types in DOMPurify config when your CSP supports it for extra defense-in-depth.

Based on learnings

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

36-36: Confirm no overflow clipping of the profile panel

With an absolute bottom-full panel inside ancestors that have overflow-hidden, tall panels can clip. Please verify on short viewports and with many notifications; if clipping occurs, consider portaling the panel or removing overflow-hidden from the immediate ancestor.

Also applies to: 40-42

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

88-95: Use cn for class composition (consistency and merge safety)

Standardize on cn here to match the rest of the file and ensure tailwind-merge deduping.

- import { clsx } from "clsx";
+ // clsx not needed; use cn for consistency
@@
- className={clsx([
+ className={cn([
   "absolute left-1/2 transform -translate-x-1/2",
   "rounded-full bg-white hover:bg-neutral-50",
   "text-neutral-700 border border-neutral-200",
   "z-20 flex items-center gap-1",
   "shadow-[inset_0_-4px_6px_-1px_rgba(255,0,0,0.1),inset_0_-2px_4px_-2px_rgba(255,0,0,0.1)]",
   isScrolledPastToday ? "top-2" : "bottom-2",
 ])}
apps/desktop/src/components/main/sidebar/profile/index.tsx (2)

122-132: Add aria-expanded/aria-controls for the toggle

Improve accessibility of the expandable panel by linking the button to the panel and exposing state.

- <div ref={profileRef} className="relative">
+ <div ref={profileRef} className="relative">
@@
-   <motion.div
+   <motion.div
+     id="profile-panel"
      initial={{ opacity: 0, y: 10 }}
@@
- <ProfileButton isExpanded={isExpanded} onClick={() => setIsExpanded(!isExpanded)} />
+ <ProfileButton
+   isExpanded={isExpanded}
+   onClick={() => setIsExpanded(!isExpanded)}
+   aria-controls="profile-panel"
+   aria-expanded={isExpanded}
+ />

Also applies to: 175-177


184-193: Prefer cn and remove conflicting border color utilities

Two border colors are specified (“border-white/60” and “border-neutral-400”); the latter overrides the former. Clean up and use cn for merge safety.

- import { clsx } from "clsx";
+ import { cn } from "@hypr/utils";
@@
- <button
-   className={clsx(
+ <button
+   className={cn([
      "flex w-full items-center gap-2.5",
      "px-4 py-2",
      "text-left",
      "transition-all duration-300",
-     "hover:bg-neutral-100",
-     isExpanded && "bg-neutral-50 border-t border-neutral-100",
-   )}
+     "hover:bg-neutral-100",
+     isExpanded && "bg-neutral-50 border-t border-neutral-100",
+   ])}
@@
- <div
-   className={clsx(
+ <div
+   className={cn([
      "flex size-8 flex-shrink-0 items-center justify-center",
      "overflow-hidden rounded-full",
-     "border border-white/60 border-t border-neutral-400",
+     "border border-neutral-200",
      "bg-gradient-to-br from-indigo-400 to-purple-500",
      "shadow-sm",
      "transition-transform duration-300",
-   )}
+   ])}

Also applies to: 196-203

📜 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 a3e90ea and a9078f5.

⛔ Files ignored due to path filters (7)
  • apps/desktop/public/assets/conferencing-platforms/meet.png is excluded by !**/*.png
  • apps/desktop/public/assets/conferencing-platforms/teams.png is excluded by !**/*.png
  • apps/desktop/public/assets/conferencing-platforms/webex.png is excluded by !**/*.png
  • apps/desktop/public/assets/conferencing-platforms/zoom.png is excluded by !**/*.png
  • packages/ui/src/components/ui/button-group.tsx is excluded by !packages/ui/src/components/ui/**
  • packages/ui/src/components/ui/hover-card.tsx is excluded by !packages/ui/src/components/ui/**
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (39)
  • apps/desktop/src/components/chat/input.tsx (1 hunks)
  • apps/desktop/src/components/chat/message/normal.tsx (2 hunks)
  • apps/desktop/src/components/chat/message/shared.tsx (4 hunks)
  • apps/desktop/src/components/chat/message/tool/search.tsx (1 hunks)
  • apps/desktop/src/components/main/body/contacts/details.tsx (13 hunks)
  • apps/desktop/src/components/main/body/index.tsx (1 hunks)
  • apps/desktop/src/components/main/body/search.tsx (3 hunks)
  • apps/desktop/src/components/main/body/sessions/floating/listen.tsx (5 hunks)
  • apps/desktop/src/components/main/body/sessions/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/metadata.tsx (10 hunks)
  • apps/desktop/src/components/main/body/sessions/outer-header/other.tsx (1 hunks)
  • apps/desktop/src/components/main/body/sessions/outer-header/share.tsx (4 hunks)
  • apps/desktop/src/components/main/sidebar/index.tsx (1 hunks)
  • apps/desktop/src/components/main/sidebar/profile/banner.tsx (2 hunks)
  • apps/desktop/src/components/main/sidebar/profile/index.tsx (3 hunks)
  • apps/desktop/src/components/main/sidebar/search/group.tsx (2 hunks)
  • apps/desktop/src/components/main/sidebar/search/index.tsx (2 hunks)
  • apps/desktop/src/components/main/sidebar/search/item.tsx (4 hunks)
  • apps/desktop/src/components/main/sidebar/timeline/index.tsx (4 hunks)
  • apps/desktop/src/components/main/sidebar/timeline/item.tsx (3 hunks)
  • apps/desktop/src/components/settings/ai/llm/configure.tsx (1 hunks)
  • apps/desktop/src/components/settings/ai/llm/select.tsx (2 hunks)
  • apps/desktop/src/components/settings/ai/stt/configure.tsx (5 hunks)
  • apps/desktop/src/components/settings/ai/stt/select.tsx (2 hunks)
  • apps/desktop/src/components/settings/general.tsx (5 hunks)
  • apps/desktop/src/components/settings/notification.tsx (1 hunks)
  • apps/desktop/src/components/settings/templates.tsx (2 hunks)
  • apps/desktop/src/contexts/audio-player/timeline.tsx (1 hunks)
  • apps/desktop/src/routes/app/auth.tsx (1 hunks)
  • apps/desktop/src/routes/app/onboarding.tsx (5 hunks)
  • apps/desktop/src/routes/app/settings/_layout.tsx (4 hunks)
  • apps/web/src/routes/callback/auth.tsx (1 hunks)
  • packages/tiptap/src/editor/index.tsx (0 hunks)
  • packages/ui/components.json (1 hunks)
  • packages/ui/package.json (1 hunks)
  • packages/utils/src/cn.ts (1 hunks)
  • plugins/auth/templates/callback.jinja (1 hunks)
  • plugins/listener/assets/viewer.html (7 hunks)
💤 Files with no reviewable changes (1)
  • packages/tiptap/src/editor/index.tsx
🧰 Additional context used
📓 Path-based instructions (2)
apps/desktop/**/*.{tsx,jsx}

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

apps/desktop/**/*.{tsx,jsx}: When there are many className values with conditional logic in React components, use the cn utility imported as import { cn } from "@hypr/ui/lib/utils"
When using cn, always pass an array of class segments (e.g., cn([ ... ]))
When using cn, split the array by logical grouping of classes for readability

Files:

  • apps/desktop/src/components/main/sidebar/index.tsx
  • apps/desktop/src/components/main/body/sessions/note-input/raw.tsx
  • apps/desktop/src/components/chat/message/tool/search.tsx
  • apps/desktop/src/components/chat/message/shared.tsx
  • apps/desktop/src/components/settings/ai/stt/select.tsx
  • apps/desktop/src/components/main/body/search.tsx
  • apps/desktop/src/components/main/body/sessions/index.tsx
  • apps/desktop/src/components/settings/notification.tsx
  • apps/desktop/src/contexts/audio-player/timeline.tsx
  • apps/desktop/src/components/main/sidebar/search/group.tsx
  • apps/desktop/src/routes/app/auth.tsx
  • apps/desktop/src/components/settings/ai/llm/configure.tsx
  • apps/desktop/src/components/chat/message/normal.tsx
  • apps/desktop/src/routes/app/settings/_layout.tsx
  • apps/desktop/src/components/main/body/index.tsx
  • apps/desktop/src/components/main/sidebar/search/item.tsx
  • apps/desktop/src/components/main/body/sessions/outer-header/share.tsx
  • apps/desktop/src/components/settings/templates.tsx
  • apps/desktop/src/components/main/sidebar/search/index.tsx
  • apps/desktop/src/components/main/body/sessions/floating/listen.tsx
  • apps/desktop/src/components/chat/input.tsx
  • apps/desktop/src/components/main/body/sessions/outer-header/other.tsx
  • apps/desktop/src/routes/app/onboarding.tsx
  • apps/desktop/src/components/settings/ai/llm/select.tsx
  • apps/desktop/src/components/main/sidebar/timeline/index.tsx
  • apps/desktop/src/components/main/sidebar/timeline/item.tsx
  • apps/desktop/src/components/settings/general.tsx
  • apps/desktop/src/components/main/body/contacts/details.tsx
  • apps/desktop/src/components/main/sidebar/profile/banner.tsx
  • apps/desktop/src/components/settings/ai/stt/configure.tsx
  • apps/desktop/src/components/main/sidebar/profile/index.tsx
  • apps/desktop/src/components/main/body/sessions/outer-header/metadata.tsx
apps/web/**/*.{ts,tsx}

📄 CodeRabbit inference engine (apps/web/.cursorrules)

apps/web/**/*.{ts,tsx}: Instrument server functions (e.g., those created via createServerFn) by wrapping their implementation with Sentry.startSpan.
When instrumenting, import Sentry from '@sentry/tanstackstart-react' using: import * as Sentry from '@sentry/tanstackstart-react'.
Provide a clear, descriptive name for each Sentry.startSpan (e.g., 'Requesting all the pokemon').

Files:

  • apps/web/src/routes/callback/auth.tsx
🧠 Learnings (3)
📚 Learning: 2025-10-20T06:06:12.693Z
Learnt from: CR
PR: fastrepl/hyprnote#0
File: apps/desktop/.cursor/rules/style.mdc:0-0
Timestamp: 2025-10-20T06:06:12.693Z
Learning: Applies to apps/desktop/**/*.{tsx,jsx} : When using `cn`, always pass an array of class segments (e.g., `cn([ ... ])`)

Applied to files:

  • packages/utils/src/cn.ts
📚 Learning: 2025-10-20T06:06:12.693Z
Learnt from: CR
PR: fastrepl/hyprnote#0
File: apps/desktop/.cursor/rules/style.mdc:0-0
Timestamp: 2025-10-20T06:06:12.693Z
Learning: Applies to apps/desktop/**/*.{tsx,jsx} : When there are many className values with conditional logic in React components, use the `cn` utility imported as `import { cn } from "hypr/ui/lib/utils"`

Applied to files:

  • packages/utils/src/cn.ts
  • apps/desktop/src/components/main/sidebar/search/group.tsx
  • apps/desktop/src/components/main/sidebar/search/item.tsx
📚 Learning: 2025-10-20T06:06:12.693Z
Learnt from: CR
PR: fastrepl/hyprnote#0
File: apps/desktop/.cursor/rules/style.mdc:0-0
Timestamp: 2025-10-20T06:06:12.693Z
Learning: Applies to apps/desktop/**/*.{tsx,jsx} : When using `cn`, split the array by logical grouping of classes for readability

Applied to files:

  • packages/utils/src/cn.ts
🧬 Code graph analysis (17)
apps/desktop/src/components/main/sidebar/index.tsx (3)
apps/desktop/src/components/main/sidebar/search/index.tsx (1)
  • SearchResults (7-19)
apps/desktop/src/components/main/sidebar/timeline/index.tsx (1)
  • TimelineView (15-104)
apps/desktop/src/components/main/sidebar/profile/index.tsx (1)
  • ProfileSection (17-180)
apps/desktop/src/components/chat/message/tool/search.tsx (1)
packages/ui/src/components/ui/card.tsx (2)
  • Card (9-23)
  • CardContent (80-93)
apps/desktop/src/components/chat/message/shared.tsx (1)
packages/utils/src/cn.ts (1)
  • cn (20-22)
apps/desktop/src/components/main/body/search.tsx (1)
packages/utils/src/cn.ts (1)
  • cn (20-22)
apps/desktop/src/components/main/body/sessions/index.tsx (3)
apps/desktop/src/components/main/body/sessions/title-input.tsx (1)
  • TitleInput (6-32)
apps/desktop/src/components/main/body/sessions/note-input/index.tsx (1)
  • NoteInput (13-39)
apps/desktop/src/components/main/body/sessions/floating/index.tsx (1)
  • FloatingActionButton (10-32)
apps/desktop/src/contexts/audio-player/timeline.tsx (1)
packages/utils/src/cn.ts (1)
  • cn (20-22)
apps/desktop/src/components/main/sidebar/search/group.tsx (1)
packages/utils/src/cn.ts (1)
  • cn (20-22)
apps/desktop/src/components/settings/ai/llm/configure.tsx (1)
packages/utils/src/cn.ts (1)
  • cn (20-22)
apps/desktop/src/components/main/sidebar/search/item.tsx (1)
packages/utils/src/cn.ts (1)
  • cn (20-22)
apps/desktop/src/components/main/body/sessions/outer-header/share.tsx (3)
packages/ui/src/components/ui/popover.tsx (1)
  • PopoverContent (29-29)
packages/ui/src/components/ui/button.tsx (1)
  • Button (53-53)
packages/ui/src/components/ui/select.tsx (1)
  • SelectItem (147-147)
apps/desktop/src/components/main/sidebar/search/index.tsx (1)
packages/utils/src/cn.ts (1)
  • cn (20-22)
apps/desktop/src/components/main/body/sessions/outer-header/other.tsx (2)
packages/ui/src/components/ui/dropdown-menu.tsx (5)
  • DropdownMenuItem (179-179)
  • DropdownMenuSub (186-186)
  • DropdownMenuSubTrigger (188-188)
  • DropdownMenuSubContent (187-187)
  • DropdownMenuSeparator (184-184)
packages/ui/src/components/ui/switch.tsx (1)
  • Switch (57-57)
apps/desktop/src/routes/app/onboarding.tsx (2)
packages/ui/src/components/ui/text-animate.tsx (1)
  • TextAnimate (302-386)
packages/utils/src/cn.ts (1)
  • cn (20-22)
apps/desktop/src/components/main/sidebar/timeline/item.tsx (1)
packages/ui/src/components/ui/context-menu.tsx (1)
  • ContextMenuItem (181-181)
apps/desktop/src/components/settings/ai/stt/configure.tsx (1)
packages/utils/src/cn.ts (1)
  • cn (20-22)
apps/desktop/src/components/main/sidebar/profile/index.tsx (2)
apps/desktop/src/components/main/sidebar/profile/notification.tsx (2)
  • NotificationsMenuHeader (10-19)
  • NotificationsMenuContent (41-75)
apps/desktop/src/components/main/sidebar/profile/banner.tsx (1)
  • TryProBanner (7-70)
apps/desktop/src/components/main/body/sessions/outer-header/metadata.tsx (3)
packages/utils/src/cn.ts (1)
  • cn (20-22)
packages/ui/src/components/ui/dropdown-menu.tsx (4)
  • DropdownMenu (175-175)
  • DropdownMenuTrigger (189-189)
  • DropdownMenuContent (177-177)
  • DropdownMenuItem (179-179)
packages/utils/src/date.ts (1)
  • getMeetingDomain (84-91)
🪛 ast-grep (0.39.6)
apps/desktop/src/components/main/sidebar/search/item.tsx

[warning] 61-61: Usage of dangerouslySetInnerHTML detected. This bypasses React's built-in XSS protection. Always sanitize HTML content using libraries like DOMPurify before injecting it into the DOM to prevent XSS attacks.
Context: dangerouslySetInnerHTML
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation [REFERENCES]
- https://reactjs.org/docs/dom-elements.html#dangerouslysetinnerhtml
- https://cwe.mitre.org/data/definitions/79.html

(react-unsafe-html-injection)


[warning] 99-99: Usage of dangerouslySetInnerHTML detected. This bypasses React's built-in XSS protection. Always sanitize HTML content using libraries like DOMPurify before injecting it into the DOM to prevent XSS attacks.
Context: dangerouslySetInnerHTML
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation [REFERENCES]
- https://reactjs.org/docs/dom-elements.html#dangerouslysetinnerhtml
- https://cwe.mitre.org/data/definitions/79.html

(react-unsafe-html-injection)


[warning] 160-160: Usage of dangerouslySetInnerHTML detected. This bypasses React's built-in XSS protection. Always sanitize HTML content using libraries like DOMPurify before injecting it into the DOM to prevent XSS attacks.
Context: dangerouslySetInnerHTML
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation [REFERENCES]
- https://reactjs.org/docs/dom-elements.html#dangerouslysetinnerhtml
- https://cwe.mitre.org/data/definitions/79.html

(react-unsafe-html-injection)

🪛 Biome (2.1.2)
apps/desktop/src/components/main/sidebar/search/item.tsx

[error] 100-100: Avoid passing content using the dangerouslySetInnerHTML prop.

Setting content using code can expose users to cross-site scripting (XSS) attacks

(lint/security/noDangerouslySetInnerHtml)


[error] 161-161: Avoid passing content using the dangerouslySetInnerHTML prop.

Setting content using code can expose users to cross-site scripting (XSS) attacks

(lint/security/noDangerouslySetInnerHtml)

🪛 GitHub Actions: .github/workflows/desktop2_ci.yaml
apps/desktop/src/components/main/body/sessions/outer-header/metadata.tsx

[error] 168-168: TS6133: 'a' is declared but its value is never read.

🔇 Additional comments (42)
packages/utils/src/cn.ts (1)

17-17: Documentation update aligns with PR theme.

The example in the JSDoc has been updated to use bg-neutral-100 instead of bg-gray-100, reflecting the broader gray → neutral color token migration. The documentation remains accurate and demonstrates valid cn usage patterns. The function implementation is unchanged and correct.

plugins/listener/assets/viewer.html (1)

9-9: Theme migration from gray to neutral color tokens is clean and consistent.

All gray-based Tailwind color tokens have been systematically replaced with their neutral equivalents throughout the file (e.g., gray-900→neutral-900, gray-600→neutral-600, etc.). The opacity modifiers and class structure are preserved correctly. The functional behavior of the component remains unchanged.

Also applies to: 74-74, 85-85, 89-89, 93-93, 102-105, 113-117, 120-123, 132-137, 151-151, 155-155, 162-164, 171-171

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

52-56: Good use of cn with logical array grouping.

The conditional className logic is cleanly organized with logical segments (flex layout, spacing/border/background, border style).


54-54: Color token migration applied correctly.

The changes from gray to neutral palette (border-gray-500border-neutral-500, bg-gray-50bg-neutral-50, text-gray-500text-neutral-500) align with the PR's systematic theme update.

Also applies to: 90-90

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

18-32: Color token migration is consistent and well-structured.

The neutral-color palette migration is applied correctly across all styling contexts (backgrounds, borders, text, hover states). The cn() calls properly use array syntax with logical grouping by category (layout, sizing, colors, effects). No behavioral changes introduced.

plugins/auth/templates/callback.jinja (1)

11-11: LGTM! Color token migration is consistent.

The migration from gray-* to neutral-* Tailwind classes is correctly applied and aligns with the PR's objective to update the color scheme across the codebase.

Also applies to: 13-13, 19-19

apps/desktop/src/components/main/body/sessions/floating/listen.tsx (4)

18-19: LGTM: Teams type added to RemoteMeeting union.

The addition of the "teams" type follows the existing pattern and maintains type safety.


85-94: Teams implementation follows the correct pattern.

The Teams button implementation is consistent with the existing Zoom, Google Meet, and Webex blocks. However, note that this code won't be reached until the useRemoteMeeting function (lines 148-158) is updated to properly detect Teams meetings instead of always returning google-meet.


139-139: LGTM: Color token migration from gray to neutral.

This change aligns with the project-wide color scheme update from gray-based to neutral-based Tailwind classes mentioned in the PR objectives.


56-56: Asset paths are correctly configured—no issues found.

All four conferencing platform icon files exist at the new consolidated location: apps/desktop/public/assets/conferencing-platforms/. The absolute paths referenced in the code (/assets/conferencing-platforms/{zoom,meet,webex,teams}.png) correctly resolve within the desktop app's public directory structure. Image references will load without issues.

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

103-103: LGTM! Color token migration looks good.

The transition from gray to neutral color tokens is consistent with the PR's theming objectives.

apps/desktop/src/components/settings/notification.tsx (1)

46-46: LGTM! Consistent color token update.

The neutral color token aligns with the broader UI refresh in this PR.

packages/ui/package.json (1)

27-27: LGTM! New dependency supports hover card functionality.

The addition of @radix-ui/react-hover-card aligns with the PR objective to add hover card components.

apps/desktop/src/routes/app/auth.tsx (1)

131-133: LGTM! Border color migration is consistent.

The neutral border tokens align with the UI refresh across the PR.

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

114-114: LGTM! Good UX improvement.

Setting defaultValue="auto" ensures the Select component has a sensible initial value, preventing an undefined state on first render.

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

282-282: LGTM! Layout refinement aligns with PR objectives.

The padding removal from StandardTabWrapper consolidates spacing around tab content areas, consistent with the layout adjustments mentioned in the PR description.

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

54-54: LGTM! Consistent neutral color theming.

The border and background color updates align with the neutral palette migration across the settings UI.


90-90: LGTM! Text color update is consistent.

The separator text color change to neutral maintains visual consistency with the updated palette.

apps/web/src/routes/callback/auth.tsx (1)

27-27: LGTM! Background color update is consistent.

The neutral background token aligns with the color scheme migration applied throughout the PR.

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

72-73: LGTM on neutral palette updates.

Icon and clear-button colors align with the new scheme.

Also applies to: 96-96

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

24-24: Neutral palette migration looks good.

Header and “Load more” states updated consistently with the broader theme.

Also applies to: 37-39

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

35-35: LGTM on styling updates.

Border and heading colors match the neutral scheme.

Also applies to: 50-52

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

26-26: LGTM on neutral token updates.

Result summary and empty-state visuals are consistent.

Also applies to: 42-42, 45-45

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

78-78: LGTM on palette migration.

Header, container, and tab states use neutral tokens consistently.

Also applies to: 93-93, 119-119, 136-140

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

47-47: LGTM on neutral color updates.

Hover, titles, and helper text align with the scheme and remain readable.

Also applies to: 59-61, 90-103, 151-151, 158-158, 164-164, 169-169

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

15-18: LGTM! Proper use of cn utility with array syntax.

The cn utility is correctly used with array syntax throughout the file, with logical grouping of classes for readability. The color token migration from gray to neutral is consistent with the PR's theming changes.

Also applies to: 36-43, 65-78, 100-111

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

41-41: LGTM! Consistent color token migration.

The color token updates from gray to neutral are consistent with the broader theming changes across the PR.

Also applies to: 84-84

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

71-71: LGTM! Consistent neutral palette adoption.

The background color updates for Card and Carousel components align with the neutral color scheme migration across the PR.

Also applies to: 79-80

apps/desktop/src/components/main/body/sessions/outer-header/share.tsx (2)

78-79: Good overflow handling restructuring.

The addition of max-h-[80vh] on the PopoverContent with an inner scrollable wrapper improves modal overflow prevention, as mentioned in the PR objectives.


217-218: Good UX improvement.

Adding cursor-pointer to SelectItem options provides better visual feedback for interactive elements.

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

98-106: Context menu simplified effectively.

The context menu has been streamlined to essential actions ("Open in New Tab" and "Delete Completely"), aligning with the PR objective to minimize the context menu. The use of internal UI components improves consistency.


136-144: LGTM! Consistent neutral color migration.

The active and hover state colors have been updated to use neutral tokens, consistent with the broader theming changes.

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

74-74: LGTM! Comprehensive neutral palette migration.

The color token updates across the onboarding flow are consistent and thorough, aligning with the project-wide neutral color scheme adoption.

Also applies to: 81-81, 89-89, 281-282, 289-289, 393-414, 425-427

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

195-195: LGTM! Consistent color token migration.

The neutral color palette has been applied consistently across the contact details UI, including borders, backgrounds, and text colors.

Also applies to: 204-204, 212-212, 222-223, 229-242, 261-272, 288-299, 315-327, 343-354, 383-394, 442-513

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

67-140: Excellent hover card implementation for participant details.

The new HoverCard provides a much better UX for viewing participant information, including avatar, job title, organization, email, and LinkedIn profile. The attended/absence indicator with remove action on hover is intuitive.


174-178: Nice UX enhancement with Backspace key handling.

Allowing users to remove the last participant by pressing Backspace when the search input is empty is a familiar and intuitive keyboard interaction pattern.


537-565: Good UX improvement with DropdownMenu for meeting links.

The dropdown menu with "Open link" and "Copy link" options provides better discoverability and flexibility for users compared to a single action button. Using openUrl from Tauri is the correct approach for opening external links in desktop applications.

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

69-69: Neutral palette migration LGTM

Consistent switch to neutral tokens; cn usage with array matches guidelines.

Also applies to: 76-76

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

106-106: Neutral token updates look good

Presentation-only; consistent with palette migration.

Also applies to: 128-128, 193-193, 218-223, 261-261

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

77-77: LGTM: neutral palette + content tweak

Consistent token migration; Streamdown content formatting is fine (backslash to avoid newline).

Also applies to: 140-140, 147-147, 182-182, 205-205, 372-373

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

46-46: Neutral UI updates LGTM

Colors and z-index adjustments align with the sidebar/profile overlay changes.

Also applies to: 56-58, 60-60, 147-147

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

41-49: Verify FAB positioning after layout change

Moving FloatingActionButton inside the inner container may affect overlay/scroll behavior. Please test in enhanced/raw/transcript modes on small viewports to ensure it doesn’t overlap inputs or get clipped.

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

Caution

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

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

4-4: Replace clsx with cn utility per coding guidelines.

The coding guidelines specify that for files matching apps/desktop/**/*.{tsx,jsx}, you should use the cn utility imported from @hypr/ui/lib/utils instead of clsx.

As per coding guidelines.

Apply this diff:

-import { clsx } from "clsx";
+import { cn } from "@hypr/ui/lib/utils";

Then update usages on lines 59 and 75:

-      className={clsx([
+      className={cn([
-        className={clsx([
+        className={cn([

70-85: Remove size-6 from className or remove size="icon" prop to resolve sizing conflict.

The Button element has conflicting size directives: size="icon" sets dimensions to 36×36px (h-9 w-9), while the size-6 class in the className sets 24×24px. CSS specificity will cause the className to override the prop, resulting in an undersized icon button.

Additionally, this component should use the cn utility instead of clsx per coding guidelines (apps/desktop/**/*.{tsx,jsx} pattern), as it contains conditional logic:

className={cn([
  "flex-shrink-0 transition-opacity size-6",
  active
    ? "opacity-100 text-neutral-700"
    : "opacity-0 group-hover:opacity-100 text-neutral-500",
])}
apps/desktop/src/components/main/body/calendars.tsx (1)

49-56: Rule-of-Hooks: early return before hooks

Hooks are called after an early return, violating the rules-of-hooks. Split the component to gate rendering first, then run hooks unconditionally.

Apply:

 export function TabContentCalendar({ tab }: { tab: Tab }) {
-  if (tab.type !== "calendars") {
-    return null;
-  }
-
-  const [sidebarOpen, setSidebarOpen] = useState(false);
+  if (tab.type !== "calendars") return null;
+  return <CalendarView tab={tab as Extract<Tab, { type: "calendars" }>} />;
 }
 
+function CalendarView({ tab }: { tab: Extract<Tab, { type: "calendars" }> }) {
+  const [sidebarOpen, setSidebarOpen] = useState(false);
🧹 Nitpick comments (2)
apps/desktop/src/components/main/body/calendars.tsx (2)

66-78: Selection state can desync when calendars list changes

selectedCalendars is initialized from calendarIds once; newly added/removed calendars won’t reflect automatically.

 const [selectedCalendars, setSelectedCalendars] = useState<Set<string>>(() => new Set(calendarIds));
 
+// Keep selection in sync with the available calendars
+useEffect(() => {
+  setSelectedCalendars((prev) => {
+    const next = new Set(prev);
+    // add new ids
+    for (const id of calendarIds) next.add(id);
+    // drop removed ids
+    for (const id of Array.from(next)) if (!calendarIds.includes(id)) next.delete(id);
+    return next;
+  });
+}, [calendarIds]);

361-367: A11y: clickable divs used as buttons

Use semantic <button> for triggers to ensure keyboard and ARIA behavior.

-<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">
     ...
-  </div>
+  </button>
</PopoverTrigger>

Repeat for the other triggers in the file.

Also applies to: 389-399, 425-431, 441-449, 471-474

📜 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 a9078f5 and f25ccf6.

⛔ Files ignored due to path filters (2)
  • packages/ui/src/components/ui/kbd.tsx is excluded by !packages/ui/src/components/ui/**
  • packages/ui/src/components/ui/progressive-blur.tsx is excluded by !packages/ui/src/components/ui/**
📒 Files selected for processing (3)
  • apps/desktop/src/components/main/body/calendars.tsx (7 hunks)
  • apps/desktop/src/components/main/body/shared.tsx (1 hunks)
  • apps/desktop/src/devtool/seed/shared.ts (1 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 className values with conditional logic in React components, use the cn utility imported as import { cn } from "@hypr/ui/lib/utils"
When using cn, always pass an array of class segments (e.g., cn([ ... ]))
When using cn, split the array by logical grouping of classes for readability

Files:

  • apps/desktop/src/components/main/body/shared.tsx
  • apps/desktop/src/components/main/body/calendars.tsx
🧬 Code graph analysis (1)
apps/desktop/src/components/main/body/calendars.tsx (3)
packages/db/src/schema.ts (1)
  • calendars (74-77)
apps/desktop/src/components/main/body/index.tsx (1)
  • StandardTabWrapper (277-289)
packages/utils/src/cn.ts (1)
  • cn (20-22)
🪛 Biome (2.1.2)
apps/desktop/src/components/main/body/calendars.tsx

[error] 54-54: This hook is being called conditionally, but all hooks must be called in the exact same order in every component render.

Hooks should not be called after an early return.

For React to preserve state between calls, hooks needs to be called unconditionally and always in the same order.
See https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level

(lint/correctness/useHookAtTopLevel)


[error] 56-56: This hook is being called conditionally, but all hooks must be called in the exact same order in every component render.

Hooks should not be called after an early return.

For React to preserve state between calls, hooks needs to be called unconditionally and always in the same order.
See https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level

(lint/correctness/useHookAtTopLevel)


[error] 59-59: This hook is being called conditionally, but all hooks must be called in the exact same order in every component render.

Hooks should not be called after an early return.

For React to preserve state between calls, hooks needs to be called unconditionally and always in the same order.
See https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level

(lint/correctness/useHookAtTopLevel)


[error] 62-62: This hook is being called from a nested function, but all hooks must be called unconditionally from the top-level component.

For React to preserve state between calls, hooks needs to be called unconditionally and always in the same order.
See https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level

(lint/correctness/useHookAtTopLevel)


[error] 66-66: This hook is being called conditionally, but all hooks must be called in the exact same order in every component render.

Hooks should not be called after an early return.

For React to preserve state between calls, hooks needs to be called unconditionally and always in the same order.
See https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level

(lint/correctness/useHookAtTopLevel)


[error] 230-230: This hook is being called from a nested function, but all hooks must be called unconditionally from the top-level component.

For React to preserve state between calls, hooks needs to be called unconditionally and always in the same order.
See https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level

(lint/correctness/useHookAtTopLevel)

🪛 GitHub Actions: .github/workflows/desktop2_ci.yaml
apps/desktop/src/components/main/body/calendars.tsx

[error] 80-80: TS6133: 'monthEnd' is declared but its value is never read.

🔇 Additional comments (1)
apps/desktop/src/devtool/seed/shared.ts (1)

89-98: Calendar seed variants look good

Nice expansion of seeds; no correctness or typing issues spotted.

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: 0

♻️ Duplicate comments (6)
apps/desktop/src/components/main/body/calendars.tsx (5)

5-16: cn import and usage don't follow repo guideline

Per coding guidelines, cn must be imported from @hypr/ui/lib/utils, not from @hypr/utils. Additionally, all cn calls must use array syntax cn([...]).


18-19: Fix lucide-react icon imports—remove "Icon" suffix

The named exports don't exist in lucide-react. Use CalendarDays, Calendar, ChevronLeft, ChevronRight, FileText instead of the "-Icon" variants.


57-63: Rule-of-Hooks: calling hooks inside array map

persisted.UI.useRow is called inside map, which violates React's Rules of Hooks. This can cause unpredictable hook ordering between renders.


225-230: Rule-of-Hooks: hook inside filter callback

persisted.UI.useRow is invoked inside filter at line 227, which breaks React's Rules of Hooks and can cause hook reordering between renders.


278-296: Inconsistent cn usage and formatting issue

Two issues here:

  1. Lines 279-282 and 285-293 use cn(...) without array syntax, violating coding guidelines. All cn calls must use cn([...]).
  2. Pipeline reports dprint formatting error on lines 286-293.

Apply dprint formatting and fix cn array syntax per past review comments.

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

167-172: Placeholder sort still produces no ordering.

The unused parameters _ and __ are an improvement over a and b, but the sort function still returns 0, producing no actual sorting. The TODO comment indicates this is intentional for now.

This issue was already flagged in a previous review. Consider the suggestions from that review when implementing the actual sorting logic.

🧹 Nitpick comments (1)
apps/desktop/src/components/main/body/sessions/outer-header/metadata.tsx (1)

435-441: Correct Tauri URL handling!

Using openUrl from the Tauri opener plugin (line 436) instead of window.open is the proper approach for Tauri applications. The clipboard API usage is straightforward and correct.

Optional suggestion: Consider adding error handling for navigator.clipboard.writeText() to provide user feedback if copying fails (e.g., due to permissions).

 const handleCopyLink = useCallback((meetingLink: string) => {
-  navigator.clipboard.writeText(meetingLink);
+  navigator.clipboard.writeText(meetingLink).catch((err) => {
+    console.error("Failed to copy link:", err);
+    // Optionally show a toast notification
+  });
 }, []);
📜 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 f25ccf6 and 3ba6389.

⛔ Files ignored due to path filters (1)
  • packages/ui/src/components/ui/progressive-blur.tsx is excluded by !packages/ui/src/components/ui/**
📒 Files selected for processing (3)
  • apps/desktop/src/components/main/body/calendars.tsx (7 hunks)
  • apps/desktop/src/components/main/body/sessions/outer-header/metadata.tsx (10 hunks)
  • apps/desktop/src/components/main/body/sessions/outer-header/share.tsx (5 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 className values with conditional logic in React components, use the cn utility imported as import { cn } from "@hypr/ui/lib/utils"
When using cn, always pass an array of class segments (e.g., cn([ ... ]))
When using cn, split the array by logical grouping of classes for readability

Files:

  • apps/desktop/src/components/main/body/calendars.tsx
  • apps/desktop/src/components/main/body/sessions/outer-header/share.tsx
  • apps/desktop/src/components/main/body/sessions/outer-header/metadata.tsx
🧠 Learnings (3)
📚 Learning: 2025-10-20T06:06:12.693Z
Learnt from: CR
PR: fastrepl/hyprnote#0
File: apps/desktop/.cursor/rules/style.mdc:0-0
Timestamp: 2025-10-20T06:06:12.693Z
Learning: Applies to apps/desktop/**/*.{tsx,jsx} : When there are many className values with conditional logic in React components, use the `cn` utility imported as `import { cn } from "hypr/ui/lib/utils"`

Applied to files:

  • apps/desktop/src/components/main/body/calendars.tsx
📚 Learning: 2025-10-20T06:06:12.693Z
Learnt from: CR
PR: fastrepl/hyprnote#0
File: apps/desktop/.cursor/rules/style.mdc:0-0
Timestamp: 2025-10-20T06:06:12.693Z
Learning: Applies to apps/desktop/**/*.{tsx,jsx} : When using `cn`, split the array by logical grouping of classes for readability

Applied to files:

  • apps/desktop/src/components/main/body/calendars.tsx
📚 Learning: 2025-10-20T06:06:12.693Z
Learnt from: CR
PR: fastrepl/hyprnote#0
File: apps/desktop/.cursor/rules/style.mdc:0-0
Timestamp: 2025-10-20T06:06:12.693Z
Learning: Applies to apps/desktop/**/*.{tsx,jsx} : When using `cn`, always pass an array of class segments (e.g., `cn([ ... ])`)

Applied to files:

  • apps/desktop/src/components/main/body/calendars.tsx
🧬 Code graph analysis (3)
apps/desktop/src/components/main/body/calendars.tsx (3)
packages/db/src/schema.ts (1)
  • calendars (74-77)
apps/desktop/src/components/main/body/index.tsx (1)
  • StandardTabWrapper (277-289)
packages/utils/src/cn.ts (1)
  • cn (20-22)
apps/desktop/src/components/main/body/sessions/outer-header/share.tsx (3)
packages/ui/src/components/ui/popover.tsx (1)
  • PopoverContent (29-29)
packages/ui/src/components/ui/button.tsx (1)
  • Button (53-53)
packages/ui/src/components/ui/select.tsx (1)
  • SelectItem (147-147)
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 (1)
  • getMeetingDomain (84-91)
🪛 Biome (2.1.2)
apps/desktop/src/components/main/body/calendars.tsx

[error] 53-53: This hook is being called conditionally, but all hooks must be called in the exact same order in every component render.

Hooks should not be called after an early return.

For React to preserve state between calls, hooks needs to be called unconditionally and always in the same order.
See https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level

(lint/correctness/useHookAtTopLevel)


[error] 55-55: This hook is being called conditionally, but all hooks must be called in the exact same order in every component render.

Hooks should not be called after an early return.

For React to preserve state between calls, hooks needs to be called unconditionally and always in the same order.
See https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level

(lint/correctness/useHookAtTopLevel)


[error] 58-58: This hook is being called conditionally, but all hooks must be called in the exact same order in every component render.

Hooks should not be called after an early return.

For React to preserve state between calls, hooks needs to be called unconditionally and always in the same order.
See https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level

(lint/correctness/useHookAtTopLevel)


[error] 61-61: This hook is being called from a nested function, but all hooks must be called unconditionally from the top-level component.

For React to preserve state between calls, hooks needs to be called unconditionally and always in the same order.
See https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level

(lint/correctness/useHookAtTopLevel)


[error] 65-65: This hook is being called conditionally, but all hooks must be called in the exact same order in every component render.

Hooks should not be called after an early return.

For React to preserve state between calls, hooks needs to be called unconditionally and always in the same order.
See https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level

(lint/correctness/useHookAtTopLevel)


[error] 227-227: This hook is being called from a nested function, but all hooks must be called unconditionally from the top-level component.

For React to preserve state between calls, hooks needs to be called unconditionally and always in the same order.
See https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level

(lint/correctness/useHookAtTopLevel)

🪛 GitHub Actions: .github/workflows/fmt.yaml
apps/desktop/src/components/main/body/calendars.tsx

[error] 286-293: dprint formatting check failed. File not formatted; formatting changes were suggested. Run '~/.dprint/bin/dprint --config dprint.json' or 'dprint fmt' to fix.

⏰ 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: ci (macos, macos-14)
  • GitHub Check: zizmor
🔇 Additional comments (11)
apps/desktop/src/components/main/body/calendars.tsx (4)

83-88: LGTM: 6×7 grid calculation is correct

The calendar grid correctly includes days from adjacent months to fill a complete 6-row, 7-column layout.


103-201: LGTM: Sidebar and header UI structure is sound

The collapsible sidebar, calendar filtering, and ButtonGroup-based navigation are well-structured. Note that the calendars array used at line 114 depends on fixing the Rules of Hooks violation flagged at lines 57-63.


319-450: LGTM: Event and session popover logic is correct

The date/time formatting, linked session handling, and popover interactions are well-implemented. Icon name issues are already flagged in earlier comments.


452-487: LGTM: Overflow popover implementation is correct

The "+N more" popover correctly displays hidden events and sessions.

apps/desktop/src/components/main/body/sessions/outer-header/share.tsx (2)

78-79: Nice improvement to overflow handling!

Moving padding inside the scrollable container (line 79) while keeping max-h-[80vh] on the outer PopoverContent (line 78) ensures proper scroll behavior and prevents viewport overflow.


161-163: Good visual distinction for the Invite action.

Using variant="outline" when there are selected people helps differentiate the invite action from the default state.

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

72-139: Excellent HoverCard implementation for participant details!

The refactored ParticipantChip now provides rich participant information in a HoverCard while maintaining a compact chip appearance. The attendance indicator (CheckIcon/XIcon) and hover-revealed remove button are nice UX touches.


174-179: Nice keyboard UX improvement!

The Backspace key handling to remove the last participant when the search is empty is a familiar pattern that improves keyboard navigation efficiency.


213-247: Well-structured participant input with proper cn() usage.

The refactored UI correctly follows the coding guideline by using cn() with an array (lines 215-218) for conditional classes based on isFocused. The focus management and conditional placeholder enhance the user experience.


540-558: Great UX enhancement with the meeting link dropdown!

The DropdownMenu provides convenient access to "Open link" and "Copy link" actions while keeping the primary "Join" button visible and prominent. Using getMeetingDomain to display a friendly domain name is a nice touch.


519-520: Consistent sizing and overflow handling across components.

The PopoverContent changes match the improvements made in share.tsx, providing consistent UX and proper scroll behavior with max-h-[80vh] and inner padding on the scrollable wrapper.

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 (4)
apps/desktop/src/components/main/body/calendars.tsx (1)

48-55: Hooks after early return violate Rule‑of‑Hooks (Biome errors 53/55/58/65)

Move the type guard into a wrapper and keep all hooks inside an inner component.

Apply:

 export function TabContentCalendar({ tab }: { tab: Tab }) {
-  if (tab.type !== "calendars") {
-    return null;
-  }
-
-  const [sidebarOpen, setSidebarOpen] = useState(false);
-  const { openCurrent } = useTabs();
+  if (tab.type !== "calendars") return null;
+  return <TabContentCalendarInner tab={tab} />;
 }
+
+function TabContentCalendarInner({ tab }: { tab: Tab }) {
+  const [sidebarOpen, setSidebarOpen] = useState(false);
+  const { openCurrent } = useTabs();

And close the new function by replacing the original closing brace with } at the end of the component body.

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

63-110: Reverse‑alphabetical sort not implemented (falls back to “oldest”)

Selecting Z‑A currently uses oldest by mistake. Implement reverse‑alpha and avoid O(n·m) includes by using a Set.

 export function useSortedHumanIds(currentOrgId?: string | null) {
   const [sortOption, setSortOption] = useState<SortOption>("alphabetical");

   const allAlphabeticalIds = persisted.UI.useResultSortedRowIds(
     persisted.QUERIES.visibleHumans,
     "name",
     false,
     0,
     undefined,
     persisted.STORE_ID,
   );
+  const allReverseAlphabeticalIds = persisted.UI.useResultSortedRowIds(
+    persisted.QUERIES.visibleHumans,
+    "name",
+    true,
+    0,
+    undefined,
+    persisted.STORE_ID,
+  );
   const allNewestIds = persisted.UI.useResultSortedRowIds(
     persisted.QUERIES.visibleHumans,
     "created_at",
     true,
     0,
     undefined,
     persisted.STORE_ID,
   );
   const allOldestIds = persisted.UI.useResultSortedRowIds(
     persisted.QUERIES.visibleHumans,
     "created_at",
     false,
     0,
     undefined,
     persisted.STORE_ID,
   );

   const thisOrgHumanIds = persisted.UI.useSliceRowIds(
     persisted.INDEXES.humansByOrg,
     currentOrgId ?? "",
     persisted.STORE_ID,
   );

-  const humanIds = currentOrgId
-    ? (sortOption === "alphabetical"
-      ? allAlphabeticalIds
-      : sortOption === "newest"
-      ? allNewestIds
-      : allOldestIds).filter((id) => thisOrgHumanIds.includes(id))
-    : (sortOption === "alphabetical"
-      ? allAlphabeticalIds
-      : sortOption === "newest"
-      ? allNewestIds
-      : allOldestIds);
+  const baseIds =
+    sortOption === "alphabetical"
+      ? allAlphabeticalIds
+      : sortOption === "reverse-alphabetical"
+      ? allReverseAlphabeticalIds
+      : sortOption === "newest"
+      ? allNewestIds
+      : allOldestIds;
+
+  const humanIds = currentOrgId
+    ? baseIds.filter((id) => {
+        const set = new Set(thisOrgHumanIds);
+        return set.has(id);
+      })
+    : baseIds;

   return { humanIds, sortOption, setSortOption };
 }
apps/desktop/src/components/main/body/contacts/index.tsx (1)

49-53: Hooks after early return: remove redundant guard

ContactView returns before hooks, violating Rules of Hooks (see Biome errors). Parent already guards on tab.type.

 function ContactView({ tab }: { tab: Tab }) {
-  if (tab.type !== "contacts") {
-    return null;
-  }
apps/desktop/src/components/main/body/contacts/organizations.tsx (1)

78-113: Reverse‑alphabetical sort not implemented (falls back to “oldest”)

Support Z‑A to match the new SortOption.

 function useSortedOrganizationIds() {
   const [sortOption, setSortOption] = useState<SortOption>("alphabetical");

   const alphabeticalIds = persisted.UI.useResultSortedRowIds(
     persisted.QUERIES.visibleOrganizations,
     "name",
     false,
     0,
     undefined,
     persisted.STORE_ID,
   );
+  const reverseAlphabeticalIds = persisted.UI.useResultSortedRowIds(
+    persisted.QUERIES.visibleOrganizations,
+    "name",
+    true,
+    0,
+    undefined,
+    persisted.STORE_ID,
+  );
   const newestIds = persisted.UI.useResultSortedRowIds(
     persisted.QUERIES.visibleOrganizations,
     "created_at",
     true,
     0,
     undefined,
     persisted.STORE_ID,
   );
   const oldestIds = persisted.UI.useResultSortedRowIds(
     persisted.QUERIES.visibleOrganizations,
     "created_at",
     false,
     0,
     undefined,
     persisted.STORE_ID,
   );

-  const organizationIds = sortOption === "alphabetical"
-    ? alphabeticalIds
-    : sortOption === "newest"
-    ? newestIds
-    : oldestIds;
+  const organizationIds =
+    sortOption === "alphabetical"
+      ? alphabeticalIds
+      : sortOption === "reverse-alphabetical"
+      ? reverseAlphabeticalIds
+      : sortOption === "newest"
+      ? newestIds
+      : oldestIds;

   return { organizationIds, sortOption, setSortOption };
 }
♻️ Duplicate comments (3)
apps/desktop/src/components/main/body/calendars.tsx (3)

5-16: cn import path + usage do not follow desktop guideline

  • Import cn from @hypr/ui/lib/utils (not @hypr/utils).
  • Use cn([...]) arrays everywhere.

Apply:

@@
-import {
-  addDays,
-  addMonths,
-  cn,
-  eachDayOfInterval,
-  format,
-  getDay,
-  isSameDay,
-  isSameMonth,
-  startOfMonth,
-  subDays,
-} from "@hypr/utils";
+import {
+  addDays,
+  addMonths,
+  eachDayOfInterval,
+  format,
+  getDay,
+  isSameDay,
+  isSameMonth,
+  startOfMonth,
+  subDays,
+} from "@hypr/utils";
+import { cn } from "@hypr/ui/lib/utils";

Normalize non-array calls (two spots shown):

-      <div
-        className={cn(
-          "text-xs size-6 rounded-full flex items-center justify-center mb-1",
-          isToday && "bg-red-500",
-        )}
-      >
+      <div
+        className={cn([
+          "text-xs size-6 rounded-full flex items-center justify-center mb-1",
+          isToday && "bg-red-500",
+        ])}
+      >
@@
-        <span
-          className={cn(
-            isToday
-              ? "text-white font-medium"
-              : !isCurrentMonth
-              ? "text-neutral-400"
-              : isWeekend
-              ? "text-neutral-500"
-              : "text-neutral-700",
-          )}
-        >
+        <span
+          className={cn([
+            isToday
+              ? "text-white font-medium"
+              : !isCurrentMonth
+              ? "text-neutral-400"
+              : isWeekend
+              ? "text-neutral-500"
+              : "text-neutral-700",
+          ])}
+        >

As per coding guidelines

Also applies to: 279-297


18-19: Fix lucide-react imports and usages (remove “Icon” suffixes)

Use actual exports: CalendarDays, Calendar, ChevronLeft, ChevronRight, FileText, Pen. Current names won’t compile.

Apply:

-import { CalendarDaysIcon, CalendarIcon, ChevronLeftIcon, ChevronRightIcon, FileTextIcon, Pen } from "lucide-react";
+import { CalendarDays, Calendar, ChevronLeft, ChevronRight, FileText, Pen } from "lucide-react";
@@
-      icon={<CalendarIcon className="w-4 h-4" />}
+      icon={<Calendar className="w-4 h-4" />}
@@
-                <Button size="icon" variant={sidebarOpen ? "default" : "ghost"} onClick={() => setSidebarOpen(false)}>
-                <CalendarDaysIcon size={16} />
+                <Button size="icon" variant={sidebarOpen ? "default" : "ghost"} onClick={() => setSidebarOpen(false)}>
+                <CalendarDays size={16} />
@@
-                <Button size="icon" variant="ghost" onClick={() => setSidebarOpen(true)}>
-                  <CalendarDaysIcon size={16} />
+                <Button size="icon" variant="ghost" onClick={() => setSidebarOpen(true)}>
+                  <CalendarDays size={16} />
@@
-                  <ChevronLeftIcon size={16} />
+                  <ChevronLeft size={16} />
@@
-                  <ChevronRightIcon size={16} />
+                  <ChevronRight size={16} />
@@
-          <CalendarIcon className="w-2.5 h-2.5 text-neutral-500 flex-shrink-0" />
+          <Calendar className="w-2.5 h-2.5 text-neutral-500 flex-shrink-0" />
@@
-              <FileTextIcon className="size-3 text-neutral-600 flex-shrink-0" />
+              <FileText className="size-3 text-neutral-600 flex-shrink-0" />
@@
-          <FileTextIcon className="w-2.5 h-2.5 text-neutral-500 flex-shrink-0" />
+          <FileText className="w-2.5 h-2.5 text-neutral-500 flex-shrink-0" />
@@
-          <FileTextIcon className="size-3 text-neutral-600 flex-shrink-0" />
+          <FileText className="size-3 text-neutral-600 flex-shrink-0" />

Also applies to: 37-38, 108-110, 139-140, 149-150, 165-166, 359-360, 380-381, 423-424, 442-443


57-63: Hooks inside map (useRow) — Rule‑of‑Hooks violation

Don’t call persisted.UI.useRow inside array builders; move row hooks into a child component.

Apply:

-  // Fetch all calendars
-  const calendarIds = persisted.UI.useRowIds("calendars", persisted.STORE_ID);
-  const calendars = calendarIds.map((id) => ({
-    id,
-    ...persisted.UI.useRow("calendars", id, persisted.STORE_ID),
-  }));
+  // Fetch all calendar IDs once
+  const calendarIds = persisted.UI.useRowIds("calendars", persisted.STORE_ID);
@@
-                {calendars.map((calendar) => (
-                  <div key={calendar.id} className="flex items-center space-x-2">
-                    <Checkbox
-                      id={`calendar-${calendar.id}`}
-                      checked={selectedCalendars.has(calendar.id)}
-                      onCheckedChange={() => toggleCalendar(calendar.id)}
-                    />
-                    <label
-                      htmlFor={`calendar-${calendar.id}`}
-                      className="text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70 cursor-pointer"
-                    >
-                      {calendar.name}
-                    </label>
-                  </div>
-                ))}
+                {calendarIds.map((id) => (
+                  <CalendarCheckboxRow
+                    key={id}
+                    id={id}
+                    checked={selectedCalendars.has(id)}
+                    onToggle={() => toggleCalendar(id)}
+                  />
+                ))}

Add child before TabContentCalendarDay:

+function CalendarCheckboxRow(
+  { id, checked, onToggle }: { id: string; checked: boolean; onToggle: () => void },
+) {
+  const calendar = persisted.UI.useRow("calendars", id, persisted.STORE_ID);
+  return (
+    <div className="flex items-center space-x-2">
+      <Checkbox id={`calendar-${id}`} checked={checked} onCheckedChange={onToggle} />
+      <label
+        htmlFor={`calendar-${id}`}
+        className="text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70 cursor-pointer"
+      >
+        {calendar?.name ?? "Untitled"}
+      </label>
+    </div>
+  );
+}

Also applies to: 114-129

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

64-77: Selection state desync when calendars are added later

selectedCalendars initializes once and won’t auto-include new calendarIds.

Apply:

   const [selectedCalendars, setSelectedCalendars] = useState<Set<string>>(() => new Set(calendarIds));
+  // Auto-include newly created calendars without clobbering user choices
+  useEffect(() => {
+    setSelectedCalendars(prev => {
+      const next = new Set(prev);
+      calendarIds.forEach(id => next.add(id));
+      return next;
+    });
+  }, [calendarIds]);

205-218: Unused prop: isLastColumn passed but not consumed

Either use it (e.g., adjust right border) or remove from props and call site.

Example removal:

-  isLastColumn: boolean;
@@
-                    isLastColumn={dayIndex === 6}

If you intend to style last column cells differently, keep it and consume in the cell cn([...]).

Also applies to: 191-191

apps/desktop/src/components/main/body/contacts/people.tsx (2)

22-35: Avoid recomputing toLowerCase per item in filter

Compute searchLower once and reuse.

-  const filteredHumanIds = useMemo(() => {
-    if (!searchValue.trim()) {
-      return humanIds;
-    }
-
-    return humanIds.filter((id) => {
-      const human = allHumans[id];
-      const searchLower = searchValue.toLowerCase();
-      return (
-        human?.name?.toLowerCase().includes(searchLower)
-        || human?.email?.toLowerCase().includes(searchLower)
-      );
-    });
-  }, [humanIds, searchValue, allHumans]);
+  const filteredHumanIds = useMemo(() => {
+    const searchLower = searchValue.trim().toLowerCase();
+    if (!searchLower) return humanIds;
+    return humanIds.filter((id) => {
+      const human = allHumans[id];
+      return (
+        human?.name?.toLowerCase().includes(searchLower) ||
+        human?.email?.toLowerCase().includes(searchLower)
+      );
+    });
+  }, [humanIds, searchValue, allHumans]);

43-46: No‑op Add handler

onAdd={() => {}} renders a clickable control with no behavior. Either wire up “New Person” flow or hide the button.

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

79-90: Clear selection after delete to avoid dangling references

Wrap delete callbacks to also clear selected entity when it’s removed.

-  const handleDeletePerson = persisted.UI.useDelRowCallback(
+  const deletePerson = persisted.UI.useDelRowCallback(
     "humans",
     (human_id: string) => human_id,
     persisted.STORE_ID,
   );
-
-  const handleDeleteOrganization = persisted.UI.useDelRowCallback(
+  const handleDeletePerson = useCallback((id: string) => {
+    deletePerson(id);
+    if (selectedPerson === id) setSelectedPerson(null);
+  }, [deletePerson, selectedPerson, setSelectedPerson]);
+
+  const deleteOrganization = persisted.UI.useDelRowCallback(
     "organizations",
     (org_id: string) => org_id,
     persisted.STORE_ID,
   );
+  const handleDeleteOrganization = useCallback((id: string) => {
+    deleteOrganization(id);
+    if (selectedOrganization === id) setSelectedOrganization(null);
+  }, [deleteOrganization, selectedOrganization, setSelectedOrganization]);
apps/desktop/src/components/main/body/contacts/organization-details.tsx (1)

58-76: Explicit button types (optional)

Add type="button" to action buttons to avoid accidental form submission if nesting ever changes.

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

24-34: Minor: compute searchLower once

Avoid lowercasing per item.

-  const filteredOrganizationIds = React.useMemo(() => {
-    if (!searchValue.trim()) {
-      return organizationIds;
-    }
-
-    return organizationIds.filter((id) => {
-      const org = allOrgs[id];
-      return org?.name?.toLowerCase().includes(searchValue.toLowerCase());
-    });
-  }, [organizationIds, searchValue, allOrgs]);
+  const filteredOrganizationIds = React.useMemo(() => {
+    const searchLower = searchValue.trim().toLowerCase();
+    if (!searchLower) return organizationIds;
+    return organizationIds.filter((id) => {
+      const org = allOrgs[id];
+      return org?.name?.toLowerCase().includes(searchLower);
+    });
+  }, [organizationIds, searchValue, allOrgs]);

143-145: Fallback name for unnamed organizations (optional)

Show “Unnamed Organization” if name is empty for better UX consistency.

-        <p className="truncate">{organization.name}</p>
+        <p className="truncate">{organization.name || "Unnamed Organization"}</p>
📜 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 3ba6389 and e93cdc2.

📒 Files selected for processing (6)
  • apps/desktop/src/components/main/body/calendars.tsx (7 hunks)
  • apps/desktop/src/components/main/body/contacts/index.tsx (3 hunks)
  • apps/desktop/src/components/main/body/contacts/organization-details.tsx (1 hunks)
  • apps/desktop/src/components/main/body/contacts/organizations.tsx (4 hunks)
  • apps/desktop/src/components/main/body/contacts/people.tsx (4 hunks)
  • apps/desktop/src/components/main/body/contacts/shared.tsx (4 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 className values with conditional logic in React components, use the cn utility imported as import { cn } from "@hypr/ui/lib/utils"
When using cn, always pass an array of class segments (e.g., cn([ ... ]))
When using cn, split the array by logical grouping of classes for readability

Files:

  • apps/desktop/src/components/main/body/contacts/organizations.tsx
  • apps/desktop/src/components/main/body/contacts/organization-details.tsx
  • apps/desktop/src/components/main/body/contacts/shared.tsx
  • apps/desktop/src/components/main/body/contacts/people.tsx
  • apps/desktop/src/components/main/body/calendars.tsx
  • apps/desktop/src/components/main/body/contacts/index.tsx
🧠 Learnings (3)
📚 Learning: 2025-10-20T06:06:12.693Z
Learnt from: CR
PR: fastrepl/hyprnote#0
File: apps/desktop/.cursor/rules/style.mdc:0-0
Timestamp: 2025-10-20T06:06:12.693Z
Learning: Applies to apps/desktop/**/*.{tsx,jsx} : When there are many className values with conditional logic in React components, use the `cn` utility imported as `import { cn } from "hypr/ui/lib/utils"`

Applied to files:

  • apps/desktop/src/components/main/body/calendars.tsx
📚 Learning: 2025-10-20T06:06:12.693Z
Learnt from: CR
PR: fastrepl/hyprnote#0
File: apps/desktop/.cursor/rules/style.mdc:0-0
Timestamp: 2025-10-20T06:06:12.693Z
Learning: Applies to apps/desktop/**/*.{tsx,jsx} : When using `cn`, split the array by logical grouping of classes for readability

Applied to files:

  • apps/desktop/src/components/main/body/calendars.tsx
📚 Learning: 2025-10-20T06:06:12.693Z
Learnt from: CR
PR: fastrepl/hyprnote#0
File: apps/desktop/.cursor/rules/style.mdc:0-0
Timestamp: 2025-10-20T06:06:12.693Z
Learning: Applies to apps/desktop/**/*.{tsx,jsx} : When using `cn`, always pass an array of class segments (e.g., `cn([ ... ])`)

Applied to files:

  • apps/desktop/src/components/main/body/calendars.tsx
🧬 Code graph analysis (6)
apps/desktop/src/components/main/body/contacts/organizations.tsx (1)
apps/desktop/src/components/main/body/contacts/shared.tsx (1)
  • ColumnHeader (56-142)
apps/desktop/src/components/main/body/contacts/organization-details.tsx (1)
apps/desktop/src/components/main/body/contacts/shared.tsx (1)
  • getInitials (7-17)
apps/desktop/src/components/main/body/contacts/shared.tsx (1)
apps/desktop/src/components/main/body/search.tsx (1)
  • Search (8-107)
apps/desktop/src/components/main/body/contacts/people.tsx (1)
apps/desktop/src/components/main/body/contacts/shared.tsx (1)
  • ColumnHeader (56-142)
apps/desktop/src/components/main/body/calendars.tsx (3)
packages/db/src/schema.ts (1)
  • calendars (74-77)
apps/desktop/src/components/main/body/index.tsx (1)
  • StandardTabWrapper (277-289)
packages/utils/src/cn.ts (1)
  • cn (20-22)
apps/desktop/src/components/main/body/contacts/index.tsx (4)
apps/desktop/src/components/main/body/contacts/people.tsx (2)
  • useSortedHumanIds (63-110)
  • PeopleColumn (8-61)
apps/desktop/src/components/main/body/contacts/organizations.tsx (1)
  • OrganizationsColumn (9-76)
apps/desktop/src/components/main/body/contacts/organization-details.tsx (1)
  • OrganizationDetailsColumn (10-132)
apps/desktop/src/components/main/body/contacts/details.tsx (1)
  • DetailsColumn (10-162)
🪛 Biome (2.1.2)
apps/desktop/src/components/main/body/calendars.tsx

[error] 53-53: This hook is being called conditionally, but all hooks must be called in the exact same order in every component render.

Hooks should not be called after an early return.

For React to preserve state between calls, hooks needs to be called unconditionally and always in the same order.
See https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level

(lint/correctness/useHookAtTopLevel)


[error] 55-55: This hook is being called conditionally, but all hooks must be called in the exact same order in every component render.

Hooks should not be called after an early return.

For React to preserve state between calls, hooks needs to be called unconditionally and always in the same order.
See https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level

(lint/correctness/useHookAtTopLevel)


[error] 58-58: This hook is being called conditionally, but all hooks must be called in the exact same order in every component render.

Hooks should not be called after an early return.

For React to preserve state between calls, hooks needs to be called unconditionally and always in the same order.
See https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level

(lint/correctness/useHookAtTopLevel)


[error] 61-61: This hook is being called from a nested function, but all hooks must be called unconditionally from the top-level component.

For React to preserve state between calls, hooks needs to be called unconditionally and always in the same order.
See https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level

(lint/correctness/useHookAtTopLevel)


[error] 65-65: This hook is being called conditionally, but all hooks must be called in the exact same order in every component render.

Hooks should not be called after an early return.

For React to preserve state between calls, hooks needs to be called unconditionally and always in the same order.
See https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level

(lint/correctness/useHookAtTopLevel)


[error] 227-227: This hook is being called from a nested function, but all hooks must be called unconditionally from the top-level component.

For React to preserve state between calls, hooks needs to be called unconditionally and always in the same order.
See https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level

(lint/correctness/useHookAtTopLevel)

apps/desktop/src/components/main/body/contacts/index.tsx

[error] 68-68: This hook is being called conditionally, but all hooks must be called in the exact same order in every component render.

Hooks should not be called after an early return.

For React to preserve state between calls, hooks needs to be called unconditionally and always in the same order.
See https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level

(lint/correctness/useHookAtTopLevel)


[error] 85-85: This hook is being called conditionally, but all hooks must be called in the exact same order in every component render.

Hooks should not be called after an early return.

For React to preserve state between calls, hooks needs to be called unconditionally and always in the same order.
See https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level

(lint/correctness/useHookAtTopLevel)


[error] 92-92: This hook is being called conditionally, but all hooks must be called in the exact same order in every component render.

Hooks should not be called after an early return.

For React to preserve state between calls, hooks needs to be called unconditionally and always in the same order.
See https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level

(lint/correctness/useHookAtTopLevel)


[error] 95-95: This hook is being called conditionally, but all hooks must be called in the exact same order in every component render.

Hooks should not be called after an early return.

For React to preserve state between calls, hooks needs to be called unconditionally and always in the same order.
See https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level

(lint/correctness/useHookAtTopLevel)

⏰ 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 (1)
apps/desktop/src/components/main/body/contacts/index.tsx (1)

94-100: Auto‑select effect looks good

Selective first‑person auto‑pick only when neither org nor person is selected. LGTM.

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

Caution

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

⚠️ Outside diff range comments (9)
apps/desktop/src/components/main/sidebar/profile/banner.tsx (1)

5-5: Import cn from the correct path per coding guidelines.

The coding guidelines specify that cn should be imported from @hypr/ui/lib/utils, but it's currently imported from @hypr/utils.

Based on coding guidelines.

Apply this diff to fix the import path:

-import { cn } from "@hypr/utils";
+import { cn } from "@hypr/ui/lib/utils";
apps/web/src/routes/callback/auth.tsx (1)

9-10: Handle missing auth code gracefully.

If the code parameter is absent from the URL, params.get("code") returns null, which will display as "Code: null" in the UI (line 27). Consider adding a fallback or error state.

Apply this diff to add basic null handling:

  const searchParams = Route.useSearch();
  const params = new URLSearchParams(searchParams as Record<string, string>);
  const code = params.get("code");
+ 
+ if (!code) {
+   return (
+     <div className="min-h-screen bg-gradient-to-br from-blue-50 to-indigo-100 flex items-center justify-center p-4">
+       <div className="bg-white rounded-2xl shadow-xl p-8 max-w-md w-full text-center">
+         <p className="text-red-600">Error: Missing authorization code</p>
+       </div>
+     </div>
+   );
+ }
+
  const deeplink = "hypr://auth/callback?" + params.toString();
apps/desktop/src/components/main/body/sessions/floating/listen.tsx (2)

148-158: Critical: Hardcoded meeting type breaks Teams and other platform detection.

The function always returns google-meet type regardless of the session, which means:

  • The newly added Teams UI (lines 85-94) will never be displayed
  • Zoom and Webex branches are also unreachable
  • Meeting type detection is completely broken

Apply this diff to properly implement meeting type detection:

 function useRemoteMeeting(sessionId: string): RemoteMeeting | null {
   const note = persisted.UI.useCell("sessions", sessionId, "raw_md", persisted.STORE_ID);
-  console.log(note);
 
-  const remote = {
-    type: "google-meet",
-    url: null,
-  } as RemoteMeeting | null;
+  // TODO: Parse note content to detect meeting type and extract URL
+  // Example patterns: zoom.us/j/, meet.google.com/, webex.com/, teams.microsoft.com/
+  const remote = null as RemoteMeeting | null;
 
   return remote;
 }

Alternatively, if this is stub code for development, please implement the actual meeting type detection logic based on the session's note content.


150-150: Remove debug console.log statement.

Apply this diff:

-  console.log(note);
apps/desktop/src/components/main/body/calendars.tsx (1)

48-71: Critical: Rules of Hooks violation — hooks called after early return.

The early return at line 50 causes all subsequent hooks (lines 53, 55, 57, 59) to be called conditionally, violating React's Rules of Hooks. This can lead to runtime errors and state corruption.

Restructure to call hooks unconditionally before any early return:

 export function TabContentCalendar({ tab }: { tab: Tab }) {
-  if (tab.type !== "calendars") {
-    return null;
-  }
-
   const [sidebarOpen, setSidebarOpen] = useState(false);
-
   const { openCurrent } = useTabs();
-
   const calendarIds = persisted.UI.useRowIds("calendars", persisted.STORE_ID);
-
   const [selectedCalendars, setSelectedCalendars] = useState<Set<string>>(() => new Set(calendarIds));
+
+  if (tab.type !== "calendars") {
+    return null;
+  }
 
   const toggleCalendar = (calendarId: string) => {
apps/desktop/src/components/main/body/sessions/outer-header/metadata.tsx (1)

259-262: Use array format for cn utility as per coding guidelines.

The coding guidelines require using cn with an array of class segments for consistency and readability.

As per coding guidelines.

Apply this diff:

                    className={cn([
                      "flex items-center justify-between px-3 py-2 text-sm text-left transition-colors w-full",
                      selectedIndex === index ? "bg-neutral-200" : "hover:bg-neutral-100",
-                    )}
+                    ])}
apps/desktop/src/components/main/sidebar/profile/index.tsx (2)

185-192: Use cn utility instead of clsx per coding guidelines.

As per coding guidelines, when there are many className values with conditional logic, use the cn utility from @hypr/ui/lib/utils and pass an array of class segments.

Apply this diff to align with the coding guidelines:

-      className={clsx(
+      className={cn([
         "flex w-full items-center gap-2.5",
         "px-4 py-2",
         "text-left",
         "transition-all duration-300",
         "hover:bg-neutral-100",
         isExpanded && "bg-neutral-50 border-t border-neutral-100",
-      )}
+      ])}

Also add the import at the top of the file:

 import { clsx } from "clsx";
+import { cn } from "@hypr/ui/lib/utils";

Based on coding guidelines


196-203: Use cn utility for consistency.

For consistency with the coding guidelines and the rest of the component, replace clsx with cn and use array format.

Apply this diff:

-        className={clsx(
+        className={cn([
           "flex size-8 flex-shrink-0 items-center justify-center",
           "overflow-hidden rounded-full",
           "border border-white/60 border-t border-neutral-400",
           "bg-gradient-to-br from-indigo-400 to-purple-500",
           "shadow-sm",
           "transition-transform duration-300",
-        )}
+        ])}

Based on coding guidelines

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

50-52: Fix hooks-at-top-level: remove early return in ContactView

This guard makes subsequent hooks conditional and trips the linter. Parent already ensures tab.type === "contacts"; drop this block.

 function ContactView({ tab }: { tab: Tab }) {
-  if (tab.type !== "contacts") {
-    return null;
-  }
♻️ Duplicate comments (5)
apps/desktop/src/components/main/body/calendars.tsx (2)

5-16: Incorrect import source for cn utility.

Per coding guidelines, the cn utility must be imported from @hypr/ui/lib/utils, not from @hypr/utils.

As per coding guidelines

Apply this diff:

 import {
   addDays,
   addMonths,
-  cn,
   eachDayOfInterval,
   format,
   getDay,
   isSameDay,
   isSameMonth,
   startOfMonth,
   subDays,
 } from "@hypr/utils";
+import { cn } from "@hypr/ui/lib/utils";

284-301: Use array syntax for cn utility calls.

Per coding guidelines, cn must always be called with an array argument: cn([...]).

As per coding guidelines

Apply this diff:

       <div
-        className={cn(
+        className={cn([
           "text-xs size-6 rounded-full flex items-center justify-center mb-1",
           isToday && "bg-red-500",
-        )}
+        ])}
       >
         <span
-          className={cn(
+          className={cn([
             isToday
               ? "text-white font-medium"
               : !isCurrentMonth
               ? "text-neutral-400"
               : isWeekend
               ? "text-neutral-500"
               : "text-neutral-700",
-          )}
+          ])}
         >
apps/desktop/src/components/main/body/sessions/outer-header/metadata.tsx (1)

167-172: Remove the no-op sort or implement actual sorting logic.

The placeholder sort function uses unused parameter conventions (_, __) to suppress TypeScript warnings but still returns 0 for all comparisons, resulting in no actual sorting. This was flagged in previous reviews.

Either remove the .sort() call entirely:

-  const sortedParticipants = useMemo(() => {
-    return [...participants].sort((_, __) => {
-      return 0;
-    });
-  }, [participants]);
+  const sortedParticipants = useMemo(() => {
+    return [...participants];
+  }, [participants]);

Or implement actual sorting logic based on the TODO comment (e.g., by attendance or name):

   const sortedParticipants = useMemo(() => {
-    return [...participants].sort((_, __) => {
-      return 0;
+    return [...participants].sort((a, b) => {
+      // Example: sort by name
+      const nameA = a.full_name || "";
+      const nameB = b.full_name || "";
+      return nameA.localeCompare(nameB);
     });
   }, [participants]);
apps/desktop/src/components/main/body/contacts/people.tsx (1)

1-1: Align cn import with repo guideline

Switch cn import to "@hypr/ui/lib/utils".

As per coding guidelines.

-import { cn } from "@hypr/utils";
+import { cn } from "@hypr/ui/lib/utils";
apps/desktop/src/components/main/body/contacts/organizations.tsx (1)

1-1: Align cn import to UI utils

Import cn from "@hypr/ui/lib/utils".

As per coding guidelines.

-import { cn } from "@hypr/utils";
+import { cn } from "@hypr/ui/lib/utils";
🧹 Nitpick comments (13)
apps/desktop/src/components/main/sidebar/profile/banner.tsx (2)

8-12: Simplify the dismiss handler and verify the empty sign-up handler.

Two observations:

  • handleDismiss is a redundant wrapper that simply calls onDismiss(). You can pass onDismiss directly to the button's onClick prop.
  • handleSignUp is empty. Verify whether this is intentional (placeholder for future implementation) or if the implementation is missing.

Apply this diff to simplify the dismiss handler:

-  const handleDismiss = () => {
-    onDismiss();
-  };
-
   const handleSignUp = () => {};

   return (
     <AnimatePresence mode="wait">
       {!isDismissed && (
         <motion.div
           ...
         >
           <div
             ...
           >
             <Button
-              onClick={handleDismiss}
+              onClick={onDismiss}
               size="icon"
               variant="ghost"
               aria-label="Dismiss banner"
               className="absolute top-2.5 right-2.5 opacity-0 group-hover:opacity-100 transition-all duration-200"
             >

18-19: Consider simplifying identical animation props.

The initial and animate props have identical values. If no intermediate animation state is needed, you could remove the animate prop to reduce redundancy.

Apply this diff if you want to simplify:

           initial={{ opacity: 1, height: "auto", y: 0, scale: 1 }}
-          animate={{ opacity: 1, height: "auto", y: 0, scale: 1 }}
           exit={{
             opacity: 0,
             height: 0,
             y: 20,
             transition: { duration: 0.3, ease: "easeInOut" },
           }}
apps/web/src/routes/callback/auth.tsx (1)

13-21: Consider consolidating duplicate deeplink opening logic.

The window.open(deeplink) call appears in both the handleOpenApp function (line 14) and the automatic opener (line 19). You could extract this to a single helper or simply call handleOpenApp() in the setTimeout.

Apply this diff to consolidate:

  const handleOpenApp = () => {
    window.open(deeplink);
  };

  if (typeof window !== "undefined") {
    setTimeout(() => {
-     window.open(deeplink);
+     handleOpenApp();
    }, 0);
  }
apps/desktop/src/components/main/body/sessions/outer-header/share.tsx (1)

51-51: Offer to implement TODO functionality.

The TODO comments indicate that the invite and copy link functionalities are not yet implemented. These are currently logging to console.

Do you want me to help implement these features? For the copy link functionality, I can provide code that:

  • Generates a shareable session link
  • Copies it to the clipboard using the Clipboard API
  • Shows user feedback (e.g., toast notification)

For the invite functionality, I can help with:

  • Sending invite requests to a backend API
  • Handling loading and error states
  • Providing user feedback on success/failure

Would you like me to generate implementations for either of these?

Also applies to: 66-66

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

40-42: Consider lower z-index and removing redundant positioning.

Two observations:

  1. z-index of 30 seems high: This places the profile menu 3 layers above the base. Verify if a lower value (e.g., z-10) would suffice for placing the dropdown above sibling content.

  2. Redundant relative class: Since ProfileSection already has relative positioning on its root element (line 123 in profile/index.tsx), adding relative here is redundant. You can apply z-30 directly without relative:

-        <div className="relative z-30">
+        <div className="z-30">
           <ProfileSection />
         </div>
apps/desktop/src/components/chat/message/tool/search.tsx (1)

71-71: Verify theme token override and add keyboard focus styles for controls

Hard‑coding bg-neutral-50 on Card and neutral-100/200 on Carousel controls may override design tokens (bg-background) and break dark/high-contrast themes. Also add focus-visible styles for keyboard users.

Example:

-<Card className="h-full bg-neutral-50">
+<Card className="h-full"> {/* relies on bg-background token */}
...
-<CarouselPrevious className="-left-4 h-6 w-6 bg-neutral-100 hover:bg-neutral-200" />
-<CarouselNext className="-right-4 h-6 w-6 bg-neutral-100 hover:bg-neutral-200" />
+<CarouselPrevious className="-left-4 h-6 w-6 bg-neutral-100 hover:bg-neutral-200 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-neutral-400" />
+<CarouselNext className="-right-4 h-6 w-6 bg-neutral-100 hover:bg-neutral-200 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-neutral-400" />

Also applies to: 79-81

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

61-81: Add focus-visible styles to ActionButton for accessibility

Buttons should show a clear focus indicator for keyboard users.

-    <button
+    <button
       aria-label={label}
       onClick={onClick}
       className={cn([
         "absolute -top-1 -right-1",
         "opacity-0 group-hover:opacity-100",
-        "transition-opacity",
+        "transition-opacity",
         "p-1 rounded-full",
+        "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-neutral-400",
         variant === "default" && [
           "bg-neutral-200 hover:bg-neutral-300",
           "text-neutral-600 hover:text-neutral-800",
         ],

98-106: Expose disabled semantics on Disclosure

When disabled, prevent toggle and convey state to AT: aria-busy/aria-disabled and pointer-events-none.

-  return (
-    <details
-      className={cn([
+  return (
+    <details
+      aria-busy={!!disabled}
+      aria-disabled={!!disabled}
+      className={cn([
         "group px-2 py-1 my-2 border rounded-md transition-colors",
-        "cursor-pointer border-neutral-200 hover:border-neutral-300",
+        disabled ? "pointer-events-none opacity-70" : "cursor-pointer",
+        "border-neutral-200 hover:border-neutral-300",
       ])}
     >
-      <summary
+      <summary
         className={cn([
           "w-full",
           "text-xs text-neutral-500",

Also applies to: 113-126


4-4: <!-- [scratchpad_start]
[task review_file_1/1 verify_import_1/1]
Verification complete for cn import location issue.

[observations]

  • First rg search found 33 files in apps/desktop importing cn from "@hypr/utils" (incorrect)
  • Second rg search found 0 files importing from "@hypr/ui/lib/utils" (correct location)
  • Affected file in review: apps/desktop/src/components/chat/message/shared.tsx:4 is one of 33 occurrences
  • Other affected files include: onboarding.tsx, settings/_layout.tsx, devtool files, all chat components, sidebar components, settings components, contexts, etc.

[analysis]

  • Review comment is correct about the guideline (learnings confirm: use "@hypr/ui/lib/utils" for apps/desktop)
  • However, issue is systemic - 33 files need this change, not just the one reviewed
  • The import from "@hypr/utils" is not a breaking change, but violates coding guideline
  • This qualifies as optional refactoring based on guidelines (not a breaking change)

[pending]
None - verification complete.

[done]

  • Confirmed import location guideline applies to all apps/desktop files
  • Identified systemic scope: 33 files need the same import change
  • Review comment is valid but incomplete; should note broader pattern

[scratchpad_start] -->

Align cn import with repo guideline across apps/desktop

The import of cn from "@hypr/utils" violates coding guidelines for apps/desktop components. This issue exists in 33 files throughout the codebase. Change to "@hypr/ui/lib/utils" per repository standards.

-import { cn } from "@hypr/utils";
+import { cn } from "@hypr/ui/lib/utils";

Other affected files requiring the same change:

  • apps/desktop/src/routes/app/onboarding.tsx
  • apps/desktop/src/routes/app/settings/_layout.tsx
  • apps/desktop/src/devtool/index.tsx
  • apps/desktop/src/devtool/tinytick.tsx
  • apps/desktop/src/components/chat/trigger.tsx
  • apps/desktop/src/components/chat/input.tsx
  • apps/desktop/src/components/chat/interactive.tsx
  • apps/desktop/src/components/chat/header.tsx
  • apps/desktop/src/components/chat/body/index.tsx
  • apps/desktop/src/components/settings/ai/llm/select.tsx
  • apps/desktop/src/components/settings/ai/shared/model-combobox.tsx
  • apps/desktop/src/components/settings/ai/llm/configure.tsx
  • apps/desktop/src/components/settings/ai/stt/select.tsx
  • apps/desktop/src/components/main/sidebar/timeline/item.tsx
  • apps/desktop/src/components/main/sidebar/timeline/index.tsx
  • apps/desktop/src/components/main/sidebar/search/group.tsx
  • apps/desktop/src/components/main/sidebar/search/index.tsx
  • apps/desktop/src/components/main/sidebar/search/item.tsx
  • apps/desktop/src/components/settings/ai/stt/configure.tsx
  • apps/desktop/src/components/main/body/search.tsx
  • apps/desktop/src/components/main/body/index.tsx
  • apps/desktop/src/components/main/sidebar/profile/shared.tsx
  • apps/desktop/src/components/main/sidebar/profile/banner.tsx
  • apps/desktop/src/components/main/body/sessions/title-input.tsx
  • apps/desktop/src/components/main/body/contacts/organizations.tsx
  • apps/desktop/src/components/main/body/contacts/people.tsx
  • apps/desktop/src/components/main/sidebar/profile/ota/index.tsx
  • apps/desktop/src/components/main/sidebar/profile/notification.tsx
  • apps/desktop/src/components/main/body/sessions/note-input/raw.tsx
  • apps/desktop/src/contexts/audio-player/timeline.tsx
  • apps/desktop/src/components/main/body/sessions/floating/generate.tsx
  • apps/desktop/src/components/main/body/sessions/note-input/index.tsx
apps/desktop/src/components/main/body/contacts/people.tsx (1)

139-141: Tweak active border for visual consistency (optional)

border-neutral-500 may appear heavy vs surrounding 200-scale neutrals. Consider 300 or 400.

-        active ? "border-neutral-500 bg-neutral-100" : "border-transparent",
+        active ? "border-neutral-300 bg-neutral-100" : "border-transparent",
apps/desktop/src/components/main/body/contacts/index.tsx (1)

79-89: Clear selection after deletes (wrap handlers)

After deleting a person/org, keep state consistent by clearing the corresponding selection.

-  const handleDeletePerson = persisted.UI.useDelRowCallback(
-    "humans",
-    (human_id: string) => human_id,
-    persisted.STORE_ID,
-  );
+  const deletePerson = persisted.UI.useDelRowCallback(
+    "humans",
+    (human_id: string) => human_id,
+    persisted.STORE_ID,
+  );
+  const handleDeletePerson = useCallback((id: string) => {
+    deletePerson(id);
+    if (selectedPerson === id) setSelectedPerson(null);
+  }, [deletePerson, selectedPerson, setSelectedPerson]);
 
-  const handleDeleteOrganization = persisted.UI.useDelRowCallback(
-    "organizations",
-    (org_id: string) => org_id,
-    persisted.STORE_ID,
-  );
+  const deleteOrganization = persisted.UI.useDelRowCallback(
+    "organizations",
+    (org_id: string) => org_id,
+    persisted.STORE_ID,
+  );
+  const handleDeleteOrganization = useCallback((id: string) => {
+    deleteOrganization(id);
+    if (selectedOrganization === id) setSelectedOrganization(null);
+  }, [deleteOrganization, selectedOrganization, setSelectedOrganization]);
apps/desktop/src/components/main/body/contacts/shared.tsx (1)

129-135: Add aria-label to clear button

Icon-only control should be labeled.

-            <button
+            <button
               onClick={() => onSearchChange("")}
               className="p-1 rounded hover:bg-neutral-100 transition-colors flex-shrink-0"
+              aria-label="Clear search"
             >
apps/desktop/src/components/main/body/contacts/organizations.tsx (1)

47-56: A11y: Indicate current selection on “All People”

Expose active state to assistive tech.

-          <button
-            onClick={() => setSelectedOrganization(null)}
-            className={cn([
+          <button
+            onClick={() => setSelectedOrganization(null)}
+            aria-current={!selectedOrganization ? "page" : undefined}
+            className={cn([
               "w-full text-left px-3 py-2 rounded-md text-sm flex items-center gap-2 hover:bg-neutral-100 transition-colors",
               !selectedOrganization && "bg-neutral-100",
             ])}
           >
📜 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 e93cdc2 and c0aeb59.

⛔ Files ignored due to path filters (10)
  • apps/desktop/.cursor/rules/style.mdc is excluded by !**/.cursor/**
  • apps/desktop/public/assets/conferencing-platforms/meet.png is excluded by !**/*.png
  • apps/desktop/public/assets/conferencing-platforms/teams.png is excluded by !**/*.png
  • apps/desktop/public/assets/conferencing-platforms/webex.png is excluded by !**/*.png
  • apps/desktop/public/assets/conferencing-platforms/zoom.png is excluded by !**/*.png
  • packages/ui/src/components/ui/button-group.tsx is excluded by !packages/ui/src/components/ui/**
  • packages/ui/src/components/ui/hover-card.tsx is excluded by !packages/ui/src/components/ui/**
  • packages/ui/src/components/ui/kbd.tsx is excluded by !packages/ui/src/components/ui/**
  • packages/ui/src/components/ui/progressive-blur.tsx is excluded by !packages/ui/src/components/ui/**
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (47)
  • apps/desktop/src/components/chat/input.tsx (1 hunks)
  • apps/desktop/src/components/chat/message/normal.tsx (2 hunks)
  • apps/desktop/src/components/chat/message/shared.tsx (4 hunks)
  • apps/desktop/src/components/chat/message/tool/search.tsx (1 hunks)
  • apps/desktop/src/components/main/body/calendars.tsx (9 hunks)
  • apps/desktop/src/components/main/body/contacts/details.tsx (13 hunks)
  • apps/desktop/src/components/main/body/contacts/index.tsx (3 hunks)
  • apps/desktop/src/components/main/body/contacts/organization-details.tsx (1 hunks)
  • apps/desktop/src/components/main/body/contacts/organizations.tsx (6 hunks)
  • apps/desktop/src/components/main/body/contacts/people.tsx (6 hunks)
  • apps/desktop/src/components/main/body/contacts/shared.tsx (4 hunks)
  • apps/desktop/src/components/main/body/index.tsx (1 hunks)
  • apps/desktop/src/components/main/body/search.tsx (3 hunks)
  • apps/desktop/src/components/main/body/sessions/floating/listen.tsx (5 hunks)
  • apps/desktop/src/components/main/body/sessions/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/metadata.tsx (10 hunks)
  • apps/desktop/src/components/main/body/sessions/outer-header/other.tsx (1 hunks)
  • apps/desktop/src/components/main/body/sessions/outer-header/share.tsx (5 hunks)
  • apps/desktop/src/components/main/body/shared.tsx (1 hunks)
  • apps/desktop/src/components/main/sidebar/index.tsx (1 hunks)
  • apps/desktop/src/components/main/sidebar/profile/banner.tsx (2 hunks)
  • apps/desktop/src/components/main/sidebar/profile/index.tsx (3 hunks)
  • apps/desktop/src/components/main/sidebar/search/group.tsx (2 hunks)
  • apps/desktop/src/components/main/sidebar/search/index.tsx (2 hunks)
  • apps/desktop/src/components/main/sidebar/search/item.tsx (4 hunks)
  • apps/desktop/src/components/main/sidebar/timeline/index.tsx (4 hunks)
  • apps/desktop/src/components/main/sidebar/timeline/item.tsx (3 hunks)
  • apps/desktop/src/components/settings/ai/llm/configure.tsx (1 hunks)
  • apps/desktop/src/components/settings/ai/llm/select.tsx (2 hunks)
  • apps/desktop/src/components/settings/ai/stt/configure.tsx (5 hunks)
  • apps/desktop/src/components/settings/ai/stt/select.tsx (2 hunks)
  • apps/desktop/src/components/settings/general.tsx (5 hunks)
  • apps/desktop/src/components/settings/notification.tsx (1 hunks)
  • apps/desktop/src/components/settings/templates.tsx (2 hunks)
  • apps/desktop/src/contexts/audio-player/timeline.tsx (1 hunks)
  • apps/desktop/src/devtool/seed/shared.ts (1 hunks)
  • apps/desktop/src/routes/app/auth.tsx (1 hunks)
  • apps/desktop/src/routes/app/onboarding.tsx (5 hunks)
  • apps/desktop/src/routes/app/settings/_layout.tsx (4 hunks)
  • apps/web/src/routes/callback/auth.tsx (1 hunks)
  • packages/tiptap/src/editor/index.tsx (0 hunks)
  • packages/ui/components.json (1 hunks)
  • packages/ui/package.json (1 hunks)
  • packages/utils/src/cn.ts (1 hunks)
  • plugins/auth/templates/callback.jinja (1 hunks)
  • plugins/listener/assets/viewer.html (7 hunks)
💤 Files with no reviewable changes (1)
  • packages/tiptap/src/editor/index.tsx
🚧 Files skipped from review as they are similar to previous changes (21)
  • apps/desktop/src/routes/app/auth.tsx
  • apps/desktop/src/components/main/body/index.tsx
  • apps/desktop/src/components/settings/ai/llm/select.tsx
  • apps/desktop/src/components/chat/message/normal.tsx
  • apps/desktop/src/components/settings/ai/llm/configure.tsx
  • apps/desktop/src/routes/app/onboarding.tsx
  • apps/desktop/src/components/main/sidebar/search/group.tsx
  • apps/desktop/src/components/settings/ai/stt/configure.tsx
  • apps/desktop/src/components/main/body/sessions/note-input/raw.tsx
  • apps/desktop/src/components/main/sidebar/search/index.tsx
  • packages/utils/src/cn.ts
  • apps/desktop/src/components/settings/ai/stt/select.tsx
  • apps/desktop/src/components/chat/input.tsx
  • plugins/listener/assets/viewer.html
  • apps/desktop/src/components/main/body/contacts/organization-details.tsx
  • apps/desktop/src/components/settings/templates.tsx
  • apps/desktop/src/components/settings/general.tsx
  • apps/desktop/src/components/main/body/search.tsx
  • apps/desktop/src/components/main/body/sessions/outer-header/other.tsx
  • apps/desktop/src/routes/app/settings/_layout.tsx
  • apps/desktop/src/components/main/body/shared.tsx
🧰 Additional context used
📓 Path-based instructions (2)
apps/desktop/**/*.{tsx,jsx}

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

apps/desktop/**/*.{tsx,jsx}: When there are many className values with conditional logic in React components, use the cn utility imported as import { cn } from "@hypr/ui/lib/utils"
When using cn, always pass an array of class segments (e.g., cn([ ... ]))
When using cn, split the array by logical grouping of classes for readability

Files:

  • apps/desktop/src/contexts/audio-player/timeline.tsx
  • apps/desktop/src/components/main/body/contacts/organizations.tsx
  • apps/desktop/src/components/main/body/contacts/people.tsx
  • apps/desktop/src/components/chat/message/tool/search.tsx
  • apps/desktop/src/components/main/sidebar/timeline/item.tsx
  • apps/desktop/src/components/main/body/sessions/index.tsx
  • apps/desktop/src/components/main/body/contacts/details.tsx
  • apps/desktop/src/components/chat/message/shared.tsx
  • apps/desktop/src/components/main/sidebar/timeline/index.tsx
  • apps/desktop/src/components/main/body/sessions/outer-header/share.tsx
  • apps/desktop/src/components/main/body/sessions/floating/listen.tsx
  • apps/desktop/src/components/main/body/calendars.tsx
  • apps/desktop/src/components/main/body/sessions/outer-header/metadata.tsx
  • apps/desktop/src/components/main/sidebar/profile/banner.tsx
  • apps/desktop/src/components/settings/notification.tsx
  • apps/desktop/src/components/main/sidebar/profile/index.tsx
  • apps/desktop/src/components/main/sidebar/search/item.tsx
  • apps/desktop/src/components/main/sidebar/index.tsx
  • apps/desktop/src/components/main/body/contacts/shared.tsx
  • apps/desktop/src/components/main/body/contacts/index.tsx
apps/web/**/*.{ts,tsx}

📄 CodeRabbit inference engine (apps/web/.cursorrules)

apps/web/**/*.{ts,tsx}: Instrument server functions (e.g., those created via createServerFn) by wrapping their implementation with Sentry.startSpan.
When instrumenting, import Sentry from '@sentry/tanstackstart-react' using: import * as Sentry from '@sentry/tanstackstart-react'.
Provide a clear, descriptive name for each Sentry.startSpan (e.g., 'Requesting all the pokemon').

Files:

  • apps/web/src/routes/callback/auth.tsx
🧠 Learnings (3)
📚 Learning: 2025-10-20T06:06:12.693Z
Learnt from: CR
PR: fastrepl/hyprnote#0
File: apps/desktop/.cursor/rules/style.mdc:0-0
Timestamp: 2025-10-20T06:06:12.693Z
Learning: Applies to apps/desktop/**/*.{tsx,jsx} : When there are many className values with conditional logic in React components, use the `cn` utility imported as `import { cn } from "hypr/ui/lib/utils"`

Applied to files:

  • apps/desktop/src/components/main/body/contacts/organizations.tsx
  • apps/desktop/src/components/main/body/contacts/people.tsx
  • apps/desktop/src/components/main/body/calendars.tsx
  • apps/desktop/src/components/main/sidebar/search/item.tsx
📚 Learning: 2025-10-20T06:06:12.693Z
Learnt from: CR
PR: fastrepl/hyprnote#0
File: apps/desktop/.cursor/rules/style.mdc:0-0
Timestamp: 2025-10-20T06:06:12.693Z
Learning: Applies to apps/desktop/**/*.{tsx,jsx} : When using `cn`, split the array by logical grouping of classes for readability

Applied to files:

  • apps/desktop/src/components/main/body/calendars.tsx
📚 Learning: 2025-10-20T06:06:12.693Z
Learnt from: CR
PR: fastrepl/hyprnote#0
File: apps/desktop/.cursor/rules/style.mdc:0-0
Timestamp: 2025-10-20T06:06:12.693Z
Learning: Applies to apps/desktop/**/*.{tsx,jsx} : When using `cn`, always pass an array of class segments (e.g., `cn([ ... ])`)

Applied to files:

  • apps/desktop/src/components/main/body/calendars.tsx
🧬 Code graph analysis (16)
apps/desktop/src/contexts/audio-player/timeline.tsx (1)
packages/utils/src/cn.ts (1)
  • cn (20-22)
apps/desktop/src/components/main/body/contacts/organizations.tsx (1)
apps/desktop/src/components/main/body/contacts/shared.tsx (1)
  • ColumnHeader (54-140)
apps/desktop/src/components/main/body/contacts/people.tsx (1)
apps/desktop/src/components/main/body/contacts/shared.tsx (1)
  • ColumnHeader (54-140)
apps/desktop/src/components/chat/message/tool/search.tsx (1)
packages/ui/src/components/ui/card.tsx (2)
  • Card (9-23)
  • CardContent (80-93)
apps/desktop/src/components/main/body/sessions/index.tsx (3)
apps/desktop/src/components/main/body/sessions/title-input.tsx (1)
  • TitleInput (6-32)
apps/desktop/src/components/main/body/sessions/note-input/index.tsx (1)
  • NoteInput (13-39)
apps/desktop/src/components/main/body/sessions/floating/index.tsx (1)
  • FloatingActionButton (10-32)
apps/desktop/src/components/main/body/contacts/details.tsx (1)
apps/desktop/src/components/main/body/contacts/shared.tsx (1)
  • getInitials (7-17)
apps/desktop/src/components/chat/message/shared.tsx (1)
packages/utils/src/cn.ts (1)
  • cn (20-22)
apps/desktop/src/components/main/body/sessions/outer-header/share.tsx (3)
packages/ui/src/components/ui/popover.tsx (1)
  • PopoverContent (29-29)
packages/ui/src/components/ui/button.tsx (1)
  • Button (53-53)
packages/ui/src/components/ui/select.tsx (1)
  • SelectItem (147-147)
apps/desktop/src/components/main/body/sessions/floating/listen.tsx (1)
apps/desktop/src/components/main/body/sessions/floating/shared.tsx (2)
  • FloatingButton (5-29)
  • formatTime (31-35)
apps/desktop/src/components/main/body/calendars.tsx (2)
apps/desktop/src/components/main/body/index.tsx (1)
  • StandardTabWrapper (277-289)
packages/utils/src/cn.ts (1)
  • cn (20-22)
apps/desktop/src/components/main/body/sessions/outer-header/metadata.tsx (2)
apps/desktop/src/components/main/body/contacts/shared.tsx (1)
  • getInitials (7-17)
packages/utils/src/date.ts (1)
  • getMeetingDomain (84-91)
apps/desktop/src/components/main/sidebar/profile/index.tsx (4)
apps/desktop/src/components/main/sidebar/profile/notification.tsx (2)
  • NotificationsMenuHeader (10-19)
  • NotificationsMenuContent (41-75)
apps/desktop/src/components/main/sidebar/profile/ota/index.tsx (1)
  • UpdateChecker (9-140)
apps/desktop/src/components/main/sidebar/profile/shared.tsx (1)
  • MenuItem (3-49)
apps/desktop/src/components/main/sidebar/profile/banner.tsx (1)
  • TryProBanner (7-70)
apps/desktop/src/components/main/sidebar/search/item.tsx (1)
packages/utils/src/cn.ts (1)
  • cn (20-22)
apps/desktop/src/components/main/sidebar/index.tsx (3)
apps/desktop/src/components/main/sidebar/search/index.tsx (1)
  • SearchResults (7-19)
apps/desktop/src/components/main/sidebar/timeline/index.tsx (1)
  • TimelineView (19-108)
apps/desktop/src/components/main/sidebar/profile/index.tsx (1)
  • ProfileSection (17-180)
apps/desktop/src/components/main/body/contacts/shared.tsx (3)
packages/ui/src/components/ui/select.tsx (3)
  • SelectTrigger (152-152)
  • SelectContent (145-145)
  • SelectItem (147-147)
packages/ui/src/components/ui/button.tsx (1)
  • Button (53-53)
apps/desktop/src/components/main/body/search.tsx (1)
  • Search (8-107)
apps/desktop/src/components/main/body/contacts/index.tsx (4)
apps/desktop/src/components/main/body/contacts/people.tsx (2)
  • useSortedHumanIds (63-122)
  • PeopleColumn (8-61)
apps/desktop/src/components/main/body/contacts/organizations.tsx (1)
  • OrganizationsColumn (9-76)
apps/desktop/src/components/main/body/contacts/organization-details.tsx (1)
  • OrganizationDetailsColumn (10-132)
apps/desktop/src/components/main/body/contacts/details.tsx (1)
  • DetailsColumn (10-162)
🪛 ast-grep (0.39.6)
apps/desktop/src/components/main/sidebar/search/item.tsx

[warning] 61-61: Usage of dangerouslySetInnerHTML detected. This bypasses React's built-in XSS protection. Always sanitize HTML content using libraries like DOMPurify before injecting it into the DOM to prevent XSS attacks.
Context: dangerouslySetInnerHTML
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation [REFERENCES]
- https://reactjs.org/docs/dom-elements.html#dangerouslysetinnerhtml
- https://cwe.mitre.org/data/definitions/79.html

(react-unsafe-html-injection)


[warning] 99-99: Usage of dangerouslySetInnerHTML detected. This bypasses React's built-in XSS protection. Always sanitize HTML content using libraries like DOMPurify before injecting it into the DOM to prevent XSS attacks.
Context: dangerouslySetInnerHTML
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation [REFERENCES]
- https://reactjs.org/docs/dom-elements.html#dangerouslysetinnerhtml
- https://cwe.mitre.org/data/definitions/79.html

(react-unsafe-html-injection)


[warning] 160-160: Usage of dangerouslySetInnerHTML detected. This bypasses React's built-in XSS protection. Always sanitize HTML content using libraries like DOMPurify before injecting it into the DOM to prevent XSS attacks.
Context: dangerouslySetInnerHTML
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation [REFERENCES]
- https://reactjs.org/docs/dom-elements.html#dangerouslysetinnerhtml
- https://cwe.mitre.org/data/definitions/79.html

(react-unsafe-html-injection)

🪛 Biome (2.1.2)
apps/desktop/src/components/main/body/calendars.tsx

[error] 53-53: This hook is being called conditionally, but all hooks must be called in the exact same order in every component render.

Hooks should not be called after an early return.

For React to preserve state between calls, hooks needs to be called unconditionally and always in the same order.
See https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level

(lint/correctness/useHookAtTopLevel)


[error] 55-55: This hook is being called conditionally, but all hooks must be called in the exact same order in every component render.

Hooks should not be called after an early return.

For React to preserve state between calls, hooks needs to be called unconditionally and always in the same order.
See https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level

(lint/correctness/useHookAtTopLevel)


[error] 57-57: This hook is being called conditionally, but all hooks must be called in the exact same order in every component render.

Hooks should not be called after an early return.

For React to preserve state between calls, hooks needs to be called unconditionally and always in the same order.
See https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level

(lint/correctness/useHookAtTopLevel)


[error] 59-59: This hook is being called conditionally, but all hooks must be called in the exact same order in every component render.

Hooks should not be called after an early return.

For React to preserve state between calls, hooks needs to be called unconditionally and always in the same order.
See https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level

(lint/correctness/useHookAtTopLevel)

apps/desktop/src/components/main/sidebar/search/item.tsx

[error] 100-100: Avoid passing content using the dangerouslySetInnerHTML prop.

Setting content using code can expose users to cross-site scripting (XSS) attacks

(lint/security/noDangerouslySetInnerHtml)


[error] 161-161: Avoid passing content using the dangerouslySetInnerHTML prop.

Setting content using code can expose users to cross-site scripting (XSS) attacks

(lint/security/noDangerouslySetInnerHtml)

apps/desktop/src/components/main/body/contacts/index.tsx

[error] 68-68: This hook is being called conditionally, but all hooks must be called in the exact same order in every component render.

Hooks should not be called after an early return.

For React to preserve state between calls, hooks needs to be called unconditionally and always in the same order.
See https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level

(lint/correctness/useHookAtTopLevel)


[error] 85-85: This hook is being called conditionally, but all hooks must be called in the exact same order in every component render.

Hooks should not be called after an early return.

For React to preserve state between calls, hooks needs to be called unconditionally and always in the same order.
See https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level

(lint/correctness/useHookAtTopLevel)


[error] 92-92: This hook is being called conditionally, but all hooks must be called in the exact same order in every component render.

Hooks should not be called after an early return.

For React to preserve state between calls, hooks needs to be called unconditionally and always in the same order.
See https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level

(lint/correctness/useHookAtTopLevel)


[error] 95-95: This hook is being called conditionally, but all hooks must be called in the exact same order in every component render.

Hooks should not be called after an early return.

For React to preserve state between calls, hooks needs to be called unconditionally and always in the same order.
See https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level

(lint/correctness/useHookAtTopLevel)

⏰ 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 (39)
plugins/auth/templates/callback.jinja (1)

11-19: LGTM! Color scheme migration is consistent.

The migration from gray to neutral color classes is correctly applied across all text and background elements in the template, aligning with the broader theme update across the application.

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

35-35: Color scheme migration looks good.

The updates from gray to neutral color tokens (border and text) align with the PR's theme migration objective. The cn utility is correctly used with array syntax and logical grouping as per coding guidelines.

Also applies to: 50-50

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

18-18: LGTM! Consistent color scheme migration.

The color token updates from gray to neutral are applied consistently across the timeline component. All Tailwind classes are valid, and the usage of cn with array syntax and logical grouping aligns with the coding guidelines.

Also applies to: 25-26, 31-32

apps/web/src/routes/callback/auth.tsx (1)

27-27: LGTM! Theme migration applied correctly.

The styling update from bg-gray-100 to bg-neutral-100 aligns with the PR's objective to migrate the color scheme across the app.

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

41-50: Verify height constraints for NoteInput's h-full behavior.

NoteInput uses h-full on its root container (from the relevant snippet), which requires its parent to have a defined height. The new wrapper structure introduces multiple divs (p-2, mt-3 px-2, mt-2) without height constraints, which may break NoteInput's ability to fill the available vertical space.

Consider adding height constraints to the wrapper hierarchy. Apply this diff to ensure proper height flow:

-        <div className="p-2">
+        <div className="flex flex-col h-full p-2">
           <OuterHeader sessionId={tab.id} />
-          <div className="mt-3 px-2">
+          <div className="flex flex-col flex-1 mt-3 px-2">
             <TitleInput tab={tab} />
-            <div className="mt-2">
+            <div className="flex flex-col flex-1 mt-2">
               <NoteInput tab={tab} />
             </div>
             <FloatingActionButton tab={tab} />

Alternatively, verify through manual testing that the layout renders correctly with the note editor filling the available space.


43-49: Verify FloatingActionButton maintains correct floating behavior.

FloatingActionButton has been repositioned inside the mt-3 px-2 container after NoteInput. If the component uses absolute or fixed positioning to "float" over content, this structural change may affect its positioning context and visual behavior.

Please verify through manual testing that:

  • The floating button still appears in the correct position
  • It doesn't interfere with or get clipped by the NoteInput content
  • The button remains accessible when scrolling through long notes
apps/desktop/src/components/main/sidebar/search/item.tsx (1)

47-169: LGTM! Consistent theme migration from gray to neutral.

The color token updates across all three search result item types (Human, Organization, Session) are consistent and align with the PR-wide theme migration. The changes maintain visual hierarchy and interaction states while transitioning to the neutral color palette.

Note: The static analysis warnings about dangerouslySetInnerHTML are false positives—the code properly sanitizes HTML using DOMPurify with a strict policy that allows only <mark> tags with no attributes before injection.

apps/desktop/src/components/main/body/sessions/floating/listen.tsx (4)

15-19: LGTM! Teams meeting type added consistently.

The new Teams variant follows the same pattern as the existing meeting types.


85-94: LGTM! Teams support implemented consistently.

The Teams meeting type UI follows the same pattern as the other conferencing platforms.


134-143: LGTM! Color scheme updated and layout clarified.

The changes align with the PR's color scheme migration (gray → neutral) and make the conditional rendering more explicit.


56-56: Asset files verified successfully.

All conferencing platform icons exist at the expected paths:

  • zoom.png ✓
  • meet.png ✓
  • webex.png ✓
  • teams.png ✓

The asset references in the code (using /assets/conferencing-platforms/) correctly resolve to apps/desktop/public/assets/conferencing-platforms/ and all files are in place.

apps/desktop/src/components/main/body/sessions/outer-header/share.tsx (7)

7-7: LGTM! Clean import removal.

The removal of Link2Icon is correct since the icon is no longer used in the Copy link button.


78-78: LGTM! Improved layout control.

The addition of max-h-[80vh] and flex layout properties provides better viewport handling and prevents overflow issues. The width reduction to 340px tightens the UI appropriately.


78-79: LGTM! Improved scrolling architecture.

The combination of outer container with max-h-[80vh] and inner wrapper with overflow-y-auto creates a cleaner single-scroll design, avoiding nested scrolling containers. Moving padding to the inner container ensures consistent spacing during scroll.


161-161: LGTM! Appropriate variant choice.

Using variant="outline" for the Invite button provides good visual hierarchy, distinguishing it from the primary Copy link button below.


173-173: LGTM! Simplified scrolling behavior.

Removing the nested scroll container (max-h-[40vh] and overflow-y-auto) aligns with the unified scrolling strategy, preventing awkward nested scrolling and providing a smoother user experience.


217-218: LGTM! Better cursor affordance.

Adding cursor-pointer to the SelectItem options improves UX by clearly indicating these items are clickable.


227-232: LGTM! Simplified primary action button.

Removing the variant prop and icon simplifies the Copy link button, appropriately emphasizing it as the primary action.

apps/desktop/src/devtool/seed/shared.ts (1)

87-109: LGTM! Enhanced calendar name variety for seed data.

The expanded template array now includes both fixed calendar names and dynamic faker-generated names, which will produce more realistic and varied test data for the calendar UI.

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

192-207: LGTM! Proper hook encapsulation.

The CalendarCheckboxRow component correctly encapsulates the useRow hook, avoiding Rules of Hooks violations when mapping over calendar IDs.


223-240: LGTM! Correct store usage in filter.

The filter correctly uses store?.getRow() (a regular method call) rather than calling hooks inside the filter callback. The store reference is safely obtained from the useStore hook before the filter operation.


423-432: LGTM! Proper date validation.

The function correctly guards against missing and invalid dates before calling format(), preventing potential runtime crashes. The fallback string provides a clear indicator when date information is unavailable.


347-362: LGTM! Robust event time formatting.

The function properly validates both start and end dates before formatting, and intelligently handles single-day vs. multi-day events using isSameDay.

packages/ui/package.json (1)

27-27: LGTM!

The addition of @radix-ui/react-hover-card aligns with the HoverCard UI implementation introduced in this PR (e.g., apps/desktop/src/components/main/body/sessions/outer-header/metadata.tsx).

packages/ui/components.json (1)

14-14: Alias update looks good.

The utils alias change from @hypr/ui/lib/utils to @hypr/utils centralizes utility imports. Past review comments confirm that stale imports in other files were already addressed in commit c8980ae.

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

1-34: LGTM!

The imports are well-organized and correctly reference the new HoverCard, DropdownMenu components, and Tauri plugin-opener's openUrl function.


67-141: LGTM on HoverCard implementation!

The ParticipantChip component effectively combines attendance indicators with a hover card showing detailed participant information. The use of getInitials for avatar fallbacks and the conditional rendering of CheckIcon/XIcon based on attendance status are well-implemented.


213-247: LGTM on the refactored ParticipantsSection UI!

The focusable container with conditional rendering of the search field and placeholder, combined with keyboard navigation (including the backspace handler to remove the last participant), provides a clean and intuitive UX.


435-441: LGTM on the meeting link handlers!

Using openUrl from the Tauri plugin-opener for cross-platform URL opening and the native navigator.clipboard API for copying are appropriate choices.

Based on learnings.


540-566: LGTM on the DropdownMenu integration for meeting links!

The DropdownMenu provides a clean way to expose both "Open link" and "Copy link" actions, while keeping the primary "Join" button readily accessible. The use of non-null assertions is safe here since the block is conditionally rendered only when meetingMetadata.meeting_link exists.

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

50-142: LGTM on the neutral color theme migration!

The color token updates from gray to neutral (lines 50, 61, 64, 94, 95, 142) are consistent with the PR's objective of updating the color scheme. The z-index reductions (30→10, 40→20) appropriately adjust layering without impacting functionality.

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

1-105: LGTM on the context menu refactor!

The simplified context menu with "Open in New Tab" and a visually distinct "Delete Completely" option (with red hover styling) improves clarity. The color token updates to neutral align with the broader PR theme.

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

123-173: LGTM! Well-structured animation refactor.

The absolute positioning with AnimatePresence provides smooth transitions between expanded/collapsed states and main/notifications views. The height management via mainViewHeight ensures consistent panel dimensions during view switching, and the nested AnimatePresence with mode="wait" prevents visual overlap.


190-190: LGTM! Theme migration from gray to neutral.

The color updates align with the PR objective to update the color scheme from gray to neutral across the app.

Also applies to: 199-199

apps/desktop/src/components/settings/notification.tsx (1)

46-46: LGTM — presentational token update only

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

195-205: LGTM — neutral token migration looks consistent

Presentational-only changes; no logic impact observed.

Also applies to: 220-236, 260-271, 288-299, 315-327, 343-354, 442-457, 461-491, 497-512

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

103-139: Three-panel resizable layout wiring looks solid

Conditional render between organization and person details, plus auto-select logic, reads well.

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

33-35: A11y: SelectTrigger icon-only trigger LGTM

Replaces nested button with icon inside trigger. Good.

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

89-97: Reverse‑alphabetical sorting implemented correctly

Descending name query plus switch case is correct.

Also applies to: 114-120

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: 0

♻️ Duplicate comments (7)
apps/desktop/src/components/main/body/contacts/people.tsx (2)

1-1: Align cn import with repo guideline

Import cn from "@hypr/ui/lib/utils" for apps/desktop components.

As per coding guidelines.

-import { cn } from "@hypr/utils";
+import { cn } from "@hypr/ui/lib/utils";

42-42: Avoid a no‑op "Add" button

onAdd={() => {}} renders a clickable control that does nothing. Either wire it to create a contact or pass undefined to hide the button.

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

1-1: Align cn import with repo guideline

Import cn from "@hypr/ui/lib/utils".

As per coding guidelines.

-import { cn } from "@hypr/utils";
+import { cn } from "@hypr/ui/lib/utils";
apps/desktop/src/components/main/body/calendars.tsx (2)

5-16: Import cn from the correct location per coding guidelines.

The cn utility should be imported from @hypr/ui/lib/utils, not @hypr/utils.

As per coding guidelines

Apply this diff:

 import {
   addDays,
   addMonths,
-  cn,
   eachDayOfInterval,
   format,
   getDay,
   isSameDay,
   isSameMonth,
   startOfMonth,
   subDays,
 } from "@hypr/utils";
+import { cn } from "@hypr/ui/lib/utils";

283-302: Wrap cn arguments in arrays per coding guidelines.

The coding guidelines require always passing an array to cn([...]). Lines 284-287 and 290-298 are missing the array wrapper.

As per coding guidelines

Apply this diff:

       <div
-        className={cn(
+        className={cn([
           "text-xs size-6 rounded-full flex items-center justify-center mb-1",
           isToday && "bg-red-500",
-        )}
+        ])}
       >
         <span
-          className={cn(
+          className={cn([
             isToday
               ? "text-white font-medium"
               : !isCurrentMonth
               ? "text-neutral-400"
               : isWeekend
               ? "text-neutral-500"
               : "text-neutral-700",
-          )}
+          ])}
         >
           {dayNumber}
         </span>
       </div>
apps/desktop/src/components/main/body/contacts/shared.tsx (2)

19-19: UI exposes “reverse‑alphabetical”; verify hooks actually implement it

Ensure useSortedHumanIds and useSortedOrganizationIds handle "reverse-alphabetical" instead of falling back to date ordering. Please confirm and add tests.

#!/bin/bash
# Expect: each hook has an explicit "reverse-alphabetical" branch.
rg -nP 'useSorted(Human|Organization)Ids' -C6 apps/desktop/src/components/main/body/contacts \
  | sed -n '1,200p'

echo "— Checking for reverse-alphabetical occurrences —"
rg -n 'reverse-alphabetical' apps/desktop/src/components/main/body/contacts -C3

Also applies to: 40-42


5-5: Import React event types explicitly (react-jsx) to prevent TS namespace errors

Use an explicit type import and drop the React. qualifier on the handler param.

-import { useState } from "react";
+import { useState, type KeyboardEvent } from "react";
@@
-  const handleSearchKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
+  const handleSearchKeyDown = (e: KeyboardEvent<HTMLInputElement>) => {

Also applies to: 78-85

🧹 Nitpick comments (1)
apps/desktop/src/components/main/body/contacts/shared.tsx (1)

92-100: Icon-only controls: add aria-labels; expose expanded state; prevent accidental submit

  • Icon-only buttons need an accessible name (title isn’t sufficient).
  • Expose toggle state for the search disclosure.
  • Native clear button should be type="button".
           {onSearchChange && (
             <Button
               onClick={handleSearchToggle}
               size="icon"
               variant="ghost"
-              title="Search"
+              title="Search"
+              aria-label="Search"
+              aria-expanded={showSearch}
             >
               <Search size={16} />
             </Button>
           )}
@@
           <Button
             onClick={onAdd}
             size="icon"
             variant="ghost"
-            title="Add"
+            title="Add"
+            aria-label="Add"
           >
             <Plus size={16} />
           </Button>
@@
           {searchValue && (
-            <button
+            <button
+              type="button"
               onClick={() => onSearchChange("")}
               className="p-1 rounded hover:bg-neutral-100 transition-colors flex-shrink-0"
+              aria-label="Clear search"
             >
               <X className="h-4 w-4 text-neutral-400" />
             </button>
           )}

Also applies to: 106-113, 129-134

📜 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 c0aeb59 and 12e7697.

📒 Files selected for processing (5)
  • apps/desktop/src/components/main/body/calendars.tsx (9 hunks)
  • apps/desktop/src/components/main/body/contacts/organizations.tsx (6 hunks)
  • apps/desktop/src/components/main/body/contacts/people.tsx (6 hunks)
  • apps/desktop/src/components/main/body/contacts/shared.tsx (4 hunks)
  • apps/desktop/src/components/main/sidebar/index.tsx (1 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 className values with conditional logic in React components, use the cn utility imported as import { cn } from "@hypr/ui/lib/utils"
When using cn, always pass an array of class segments (e.g., cn([ ... ]))
When using cn, split the array by logical grouping of classes for readability

Files:

  • apps/desktop/src/components/main/body/contacts/organizations.tsx
  • apps/desktop/src/components/main/sidebar/index.tsx
  • apps/desktop/src/components/main/body/contacts/people.tsx
  • apps/desktop/src/components/main/body/calendars.tsx
  • apps/desktop/src/components/main/body/contacts/shared.tsx
🧠 Learnings (3)
📚 Learning: 2025-10-20T06:06:12.693Z
Learnt from: CR
PR: fastrepl/hyprnote#0
File: apps/desktop/.cursor/rules/style.mdc:0-0
Timestamp: 2025-10-20T06:06:12.693Z
Learning: Applies to apps/desktop/**/*.{tsx,jsx} : When there are many className values with conditional logic in React components, use the `cn` utility imported as `import { cn } from "hypr/ui/lib/utils"`

Applied to files:

  • apps/desktop/src/components/main/body/contacts/organizations.tsx
  • apps/desktop/src/components/main/body/contacts/people.tsx
  • apps/desktop/src/components/main/body/calendars.tsx
📚 Learning: 2025-10-20T06:06:12.693Z
Learnt from: CR
PR: fastrepl/hyprnote#0
File: apps/desktop/.cursor/rules/style.mdc:0-0
Timestamp: 2025-10-20T06:06:12.693Z
Learning: Applies to apps/desktop/**/*.{tsx,jsx} : When using `cn`, split the array by logical grouping of classes for readability

Applied to files:

  • apps/desktop/src/components/main/body/calendars.tsx
📚 Learning: 2025-10-20T06:06:12.693Z
Learnt from: CR
PR: fastrepl/hyprnote#0
File: apps/desktop/.cursor/rules/style.mdc:0-0
Timestamp: 2025-10-20T06:06:12.693Z
Learning: Applies to apps/desktop/**/*.{tsx,jsx} : When using `cn`, always pass an array of class segments (e.g., `cn([ ... ])`)

Applied to files:

  • apps/desktop/src/components/main/body/calendars.tsx
🧬 Code graph analysis (5)
apps/desktop/src/components/main/body/contacts/organizations.tsx (1)
apps/desktop/src/components/main/body/contacts/shared.tsx (1)
  • ColumnHeader (54-140)
apps/desktop/src/components/main/sidebar/index.tsx (1)
apps/desktop/src/components/main/sidebar/profile/index.tsx (1)
  • ProfileSection (17-180)
apps/desktop/src/components/main/body/contacts/people.tsx (1)
apps/desktop/src/components/main/body/contacts/shared.tsx (1)
  • ColumnHeader (54-140)
apps/desktop/src/components/main/body/calendars.tsx (7)
apps/desktop/src/store/tinybase/persisted.ts (1)
  • Calendar (112-112)
packages/ui/src/components/ui/button.tsx (1)
  • Button (53-53)
packages/ui/src/components/ui/button-group.tsx (1)
  • ButtonGroup (78-78)
apps/desktop/src/components/main/body/index.tsx (1)
  • StandardTabWrapper (277-289)
packages/ui/src/components/ui/checkbox.tsx (1)
  • Checkbox (28-28)
packages/utils/src/cn.ts (1)
  • cn (20-22)
packages/ui/src/components/ui/popover.tsx (2)
  • Popover (29-29)
  • PopoverTrigger (29-29)
apps/desktop/src/components/main/body/contacts/shared.tsx (3)
packages/ui/src/components/ui/select.tsx (3)
  • SelectTrigger (152-152)
  • SelectContent (145-145)
  • SelectItem (147-147)
packages/ui/src/components/ui/button.tsx (1)
  • Button (53-53)
apps/desktop/src/components/main/body/search.tsx (1)
  • Search (8-107)
🪛 Biome (2.1.2)
apps/desktop/src/components/main/body/calendars.tsx

[error] 53-53: This hook is being called conditionally, but all hooks must be called in the exact same order in every component render.

Hooks should not be called after an early return.

For React to preserve state between calls, hooks needs to be called unconditionally and always in the same order.
See https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level

(lint/correctness/useHookAtTopLevel)


[error] 55-55: This hook is being called conditionally, but all hooks must be called in the exact same order in every component render.

Hooks should not be called after an early return.

For React to preserve state between calls, hooks needs to be called unconditionally and always in the same order.
See https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level

(lint/correctness/useHookAtTopLevel)


[error] 57-57: This hook is being called conditionally, but all hooks must be called in the exact same order in every component render.

Hooks should not be called after an early return.

For React to preserve state between calls, hooks needs to be called unconditionally and always in the same order.
See https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level

(lint/correctness/useHookAtTopLevel)


[error] 59-59: This hook is being called conditionally, but all hooks must be called in the exact same order in every component render.

Hooks should not be called after an early return.

For React to preserve state between calls, hooks needs to be called unconditionally and always in the same order.
See https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level

(lint/correctness/useHookAtTopLevel)

⏰ 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: ci (macos, macos-14)
  • GitHub Check: zizmor
🔇 Additional comments (14)
apps/desktop/src/components/main/body/contacts/people.tsx (3)

27-34: Search filter correctly handles undefined values

The nullish coalescing operator (??) on lines 30-31 properly prevents TypeErrors when name or email are undefined. Well done fixing the previous issue.


62-62: Good: Export enables cross-component reuse

Making useSortedHumanIds public allows other components to leverage the sorted list, as indicated in the PR summary.


73-118: Reverse-alphabetical sorting implemented correctly

The new sorting option is consistently wired through both the data fetch (lines 73-80) and the selection logic (lines 107-108, 114-115).

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

29-34: Search filter correctly handles undefined values

The nullish coalescing operator (??) on line 31 properly prevents TypeErrors when name is undefined. Well done fixing the previous issue.


12-16: Detail view state properly controlled

The isViewingOrgDetails prop enables external control of the detail-view border styling, cleanly separating selection from detail-view state.

Also applies to: 129-129, 134-134, 145-147


90-118: Reverse-alphabetical sorting implemented correctly

The new sorting option is consistently wired through both the data fetch (lines 90-97) and the selection logic (lines 117-118), matching the pattern in people.tsx.

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

40-42: LGTM! Stacking context ensures proper dropdown layering.

The relative z-30 wrapper creates a stacking context for the entire ProfileSection component, ensuring its absolutely-positioned dropdown menu (which expands upward with bottom-full) appears above the TimelineView/SearchResults content. This is intentional and correct.

Note: This is distinct from the past review comment about line 36—that comment addressed a redundant relative on the parent container of both TimelineView and ProfileSection, which has since been removed. This wrapper serves a different purpose: controlling the z-order of ProfileSection relative to its siblings.

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

192-207: LGTM! CalendarCheckboxRow properly extracted.

The component correctly calls hooks at the top level, following React's Rules of Hooks. This properly addresses the previous hooks violation.


229-234: LGTM! Correct pattern for filtering events by calendar.

The code properly retrieves the store once with useStore and then uses store.getRow within the filter callback. This does not violate the Rules of Hooks since getRow is a regular method call, not a hook.


354-362: LGTM! Date validation properly guards against invalid dates.

Both formatEventTime and formatSessionTime correctly validate dates using isNaN() checks and return appropriate fallbacks, preventing runtime errors from invalid date strings.

Also applies to: 424-431


77-81: LGTM! Calendar grid calculation is correct.

The 6×7 grid (42 cells) properly includes days from adjacent months, which is standard calendar behavior. The calculation using subDays, addDays, and eachDayOfInterval correctly generates all display dates.


130-154: LGTM! ButtonGroup enhances calendar navigation UX.

The navigation controls are properly grouped using the ButtonGroup component, providing a cohesive visual presentation for Previous/Today/Next actions.


98-119: LGTM! Sidebar implementation provides clean filtering UI.

The collapsible sidebar with calendar checkboxes offers a good UX for filtering visible calendars. The toggle buttons are appropriately positioned for opening and closing the sidebar.

Also applies to: 124-127

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

33-35: Good fix: removed nested Button inside SelectTrigger

Avoids button-in-button and improves a11y. Looks right.

@yujonglee yujonglee merged commit 2526a87 into main Oct 21, 2025
7 checks passed
@yujonglee yujonglee deleted the jj-branch-24 branch October 21, 2025 03:02
This was referenced Nov 12, 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