-
Notifications
You must be signed in to change notification settings - Fork 414
Add Hover Card and Button Group Components, Refactor Structure, and Improve UI #1585
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
ComputelessComputer
commented
Oct 20, 2025
- 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)
📝 WalkthroughWalkthroughWide-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
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
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
Estimated code review effort🎯 4 (Complex) | ⏱️ ~50 minutes Possibly related PRs
Suggested reviewers
Pre-merge checks and finishing touches❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this 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 forcnutility to match coding guidelines.Per coding guidelines, the
cnutility inapps/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
useRemoteMeetingfunction has two critical issues:
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.Debug console.log: Line 150 contains a
console.log(note)that should be removed before merging.The function needs proper implementation to parse the
notedata 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 directconsole.log.Error logging via
console.logis 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}, thecnutility 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-foregroundfor descriptions andhover: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-blackwith 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.
SearchNoResultsprops type includessetQuerybut 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 useline-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 panelWith 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 toggleImprove 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 utilitiesTwo 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
⛔ Files ignored due to path filters (7)
apps/desktop/public/assets/conferencing-platforms/meet.pngis excluded by!**/*.pngapps/desktop/public/assets/conferencing-platforms/teams.pngis excluded by!**/*.pngapps/desktop/public/assets/conferencing-platforms/webex.pngis excluded by!**/*.pngapps/desktop/public/assets/conferencing-platforms/zoom.pngis excluded by!**/*.pngpackages/ui/src/components/ui/button-group.tsxis excluded by!packages/ui/src/components/ui/**packages/ui/src/components/ui/hover-card.tsxis excluded by!packages/ui/src/components/ui/**pnpm-lock.yamlis 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 thecnutility imported asimport { cn } from "@hypr/ui/lib/utils"
When usingcn, always pass an array of class segments (e.g.,cn([ ... ]))
When usingcn, split the array by logical grouping of classes for readability
Files:
apps/desktop/src/components/main/sidebar/index.tsxapps/desktop/src/components/main/body/sessions/note-input/raw.tsxapps/desktop/src/components/chat/message/tool/search.tsxapps/desktop/src/components/chat/message/shared.tsxapps/desktop/src/components/settings/ai/stt/select.tsxapps/desktop/src/components/main/body/search.tsxapps/desktop/src/components/main/body/sessions/index.tsxapps/desktop/src/components/settings/notification.tsxapps/desktop/src/contexts/audio-player/timeline.tsxapps/desktop/src/components/main/sidebar/search/group.tsxapps/desktop/src/routes/app/auth.tsxapps/desktop/src/components/settings/ai/llm/configure.tsxapps/desktop/src/components/chat/message/normal.tsxapps/desktop/src/routes/app/settings/_layout.tsxapps/desktop/src/components/main/body/index.tsxapps/desktop/src/components/main/sidebar/search/item.tsxapps/desktop/src/components/main/body/sessions/outer-header/share.tsxapps/desktop/src/components/settings/templates.tsxapps/desktop/src/components/main/sidebar/search/index.tsxapps/desktop/src/components/main/body/sessions/floating/listen.tsxapps/desktop/src/components/chat/input.tsxapps/desktop/src/components/main/body/sessions/outer-header/other.tsxapps/desktop/src/routes/app/onboarding.tsxapps/desktop/src/components/settings/ai/llm/select.tsxapps/desktop/src/components/main/sidebar/timeline/index.tsxapps/desktop/src/components/main/sidebar/timeline/item.tsxapps/desktop/src/components/settings/general.tsxapps/desktop/src/components/main/body/contacts/details.tsxapps/desktop/src/components/main/sidebar/profile/banner.tsxapps/desktop/src/components/settings/ai/stt/configure.tsxapps/desktop/src/components/main/sidebar/profile/index.tsxapps/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.tsapps/desktop/src/components/main/sidebar/search/group.tsxapps/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-100instead ofbg-gray-100, reflecting the broader gray → neutral color token migration. The documentation remains accurate and demonstrates validcnusage 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 ofcnwith 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-500→border-neutral-500,bg-gray-50→bg-neutral-50,text-gray-500→text-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-*toneutral-*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
useRemoteMeetingfunction (lines 148-158) is updated to properly detect Teams meetings instead of always returninggoogle-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-cardaligns 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
StandardTabWrapperconsolidates 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 ofcnutility with array syntax.The
cnutility 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-pointerto 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
openUrlfrom 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 LGTMConsistent 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 goodPresentation-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 tweakConsistent 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 LGTMColors 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 changeMoving 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.
apps/desktop/src/components/main/body/sessions/outer-header/metadata.tsx
Show resolved
Hide resolved
There was a problem hiding this 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: Replaceclsxwithcnutility per coding guidelines.The coding guidelines specify that for files matching
apps/desktop/**/*.{tsx,jsx}, you should use thecnutility imported from@hypr/ui/lib/utilsinstead ofclsx.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: Removesize-6from className or removesize="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 thesize-6class 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
cnutility instead ofclsxper 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 hooksHooks 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
selectedCalendarsis initialized fromcalendarIdsonce; 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 buttonsUse 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
⛔ Files ignored due to path filters (2)
packages/ui/src/components/ui/kbd.tsxis excluded by!packages/ui/src/components/ui/**packages/ui/src/components/ui/progressive-blur.tsxis 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 thecnutility imported asimport { cn } from "@hypr/ui/lib/utils"
When usingcn, always pass an array of class segments (e.g.,cn([ ... ]))
When usingcn, split the array by logical grouping of classes for readability
Files:
apps/desktop/src/components/main/body/shared.tsxapps/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 goodNice expansion of seeds; no correctness or typing issues spotted.
e4f2375 to
3ba6389
Compare
There was a problem hiding this 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 guidelinePer coding guidelines,
cnmust be imported from@hypr/ui/lib/utils, not from@hypr/utils. Additionally, allcncalls must use array syntaxcn([...]).
18-19: Fix lucide-react icon imports—remove "Icon" suffixThe named exports don't exist in lucide-react. Use
CalendarDays,Calendar,ChevronLeft,ChevronRight,FileTextinstead of the "-Icon" variants.
57-63: Rule-of-Hooks: calling hooks inside array map
persisted.UI.useRowis called insidemap, which violates React's Rules of Hooks. This can cause unpredictable hook ordering between renders.
225-230: Rule-of-Hooks: hook insidefiltercallback
persisted.UI.useRowis invoked insidefilterat line 227, which breaks React's Rules of Hooks and can cause hook reordering between renders.
278-296: Inconsistentcnusage and formatting issueTwo issues here:
- Lines 279-282 and 285-293 use
cn(...)without array syntax, violating coding guidelines. Allcncalls must usecn([...]).- Pipeline reports dprint formatting error on lines 286-293.
Apply dprint formatting and fix
cnarray 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 overaandb, 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
openUrlfrom the Tauri opener plugin (line 436) instead ofwindow.openis 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
⛔ Files ignored due to path filters (1)
packages/ui/src/components/ui/progressive-blur.tsxis 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 thecnutility imported asimport { cn } from "@hypr/ui/lib/utils"
When usingcn, always pass an array of class segments (e.g.,cn([ ... ]))
When usingcn, split the array by logical grouping of classes for readability
Files:
apps/desktop/src/components/main/body/calendars.tsxapps/desktop/src/components/main/body/sessions/outer-header/share.tsxapps/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 correctThe 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 soundThe collapsible sidebar, calendar filtering, and ButtonGroup-based navigation are well-structured. Note that the
calendarsarray 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 correctThe 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 correctThe "+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
ParticipantChipnow 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 propercn()usage.The refactored UI correctly follows the coding guideline by using
cn()with an array (lines 215-218) for conditional classes based onisFocused. 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
getMeetingDomainto 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 withmax-h-[80vh]and inner padding on the scrollable wrapper.
There was a problem hiding this 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 guardContactView 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 violationDon’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 laterselectedCalendars 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 consumedEither 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 filterCompute 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 handleronAdd={() => {}} 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 referencesWrap 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 onceAvoid 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
📒 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 thecnutility imported asimport { cn } from "@hypr/ui/lib/utils"
When usingcn, always pass an array of class segments (e.g.,cn([ ... ]))
When usingcn, split the array by logical grouping of classes for readability
Files:
apps/desktop/src/components/main/body/contacts/organizations.tsxapps/desktop/src/components/main/body/contacts/organization-details.tsxapps/desktop/src/components/main/body/contacts/shared.tsxapps/desktop/src/components/main/body/contacts/people.tsxapps/desktop/src/components/main/body/calendars.tsxapps/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 goodSelective first‑person auto‑pick only when neither org nor person is selected. LGTM.
e93cdc2 to
c0aeb59
Compare
There was a problem hiding this 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: Importcnfrom the correct path per coding guidelines.The coding guidelines specify that
cnshould 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
codeparameter is absent from the URL,params.get("code")returnsnull, 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-meettype 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 forcnutility as per coding guidelines.The coding guidelines require using
cnwith 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: Usecnutility instead ofclsxper coding guidelines.As per coding guidelines, when there are many className values with conditional logic, use the
cnutility from@hypr/ui/lib/utilsand 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: Usecnutility for consistency.For consistency with the coding guidelines and the rest of the component, replace
clsxwithcnand 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 ContactViewThis 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 forcnutility.Per coding guidelines, the
cnutility 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 forcnutility calls.Per coding guidelines,
cnmust 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 guidelineSwitch 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 utilsImport 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:
handleDismissis a redundant wrapper that simply callsonDismiss(). You can passonDismissdirectly to the button'sonClickprop.handleSignUpis 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
initialandanimateprops have identical values. If no intermediate animation state is needed, you could remove theanimateprop 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 thehandleOpenAppfunction (line 14) and the automatic opener (line 19). You could extract this to a single helper or simply callhandleOpenApp()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:
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.Redundant
relativeclass: SinceProfileSectionalready hasrelativepositioning on its root element (line 123 in profile/index.tsx), addingrelativehere is redundant. You can applyz-30directly withoutrelative:- <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 controlsHard‑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 accessibilityButtons 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 DisclosureWhen 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
cnfrom"@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 buttonIcon-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
⛔ Files ignored due to path filters (10)
apps/desktop/.cursor/rules/style.mdcis excluded by!**/.cursor/**apps/desktop/public/assets/conferencing-platforms/meet.pngis excluded by!**/*.pngapps/desktop/public/assets/conferencing-platforms/teams.pngis excluded by!**/*.pngapps/desktop/public/assets/conferencing-platforms/webex.pngis excluded by!**/*.pngapps/desktop/public/assets/conferencing-platforms/zoom.pngis excluded by!**/*.pngpackages/ui/src/components/ui/button-group.tsxis excluded by!packages/ui/src/components/ui/**packages/ui/src/components/ui/hover-card.tsxis excluded by!packages/ui/src/components/ui/**packages/ui/src/components/ui/kbd.tsxis excluded by!packages/ui/src/components/ui/**packages/ui/src/components/ui/progressive-blur.tsxis excluded by!packages/ui/src/components/ui/**pnpm-lock.yamlis 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 thecnutility imported asimport { cn } from "@hypr/ui/lib/utils"
When usingcn, always pass an array of class segments (e.g.,cn([ ... ]))
When usingcn, split the array by logical grouping of classes for readability
Files:
apps/desktop/src/contexts/audio-player/timeline.tsxapps/desktop/src/components/main/body/contacts/organizations.tsxapps/desktop/src/components/main/body/contacts/people.tsxapps/desktop/src/components/chat/message/tool/search.tsxapps/desktop/src/components/main/sidebar/timeline/item.tsxapps/desktop/src/components/main/body/sessions/index.tsxapps/desktop/src/components/main/body/contacts/details.tsxapps/desktop/src/components/chat/message/shared.tsxapps/desktop/src/components/main/sidebar/timeline/index.tsxapps/desktop/src/components/main/body/sessions/outer-header/share.tsxapps/desktop/src/components/main/body/sessions/floating/listen.tsxapps/desktop/src/components/main/body/calendars.tsxapps/desktop/src/components/main/body/sessions/outer-header/metadata.tsxapps/desktop/src/components/main/sidebar/profile/banner.tsxapps/desktop/src/components/settings/notification.tsxapps/desktop/src/components/main/sidebar/profile/index.tsxapps/desktop/src/components/main/sidebar/search/item.tsxapps/desktop/src/components/main/sidebar/index.tsxapps/desktop/src/components/main/body/contacts/shared.tsxapps/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.tsxapps/desktop/src/components/main/body/contacts/people.tsxapps/desktop/src/components/main/body/calendars.tsxapps/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
cnutility 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
cnwith 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-100tobg-neutral-100aligns 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-fullon 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-2container 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
dangerouslySetInnerHTMLare 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 toapps/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
Link2Iconis 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 withoverflow-y-autocreates 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]andoverflow-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-pointerto 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
CalendarCheckboxRowcomponent correctly encapsulates theuseRowhook, 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 theuseStorehook 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-cardaligns 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/utilsto@hypr/utilscentralizes 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
openUrlfunction.
67-141: LGTM on HoverCard implementation!The ParticipantChip component effectively combines attendance indicators with a hover card showing detailed participant information. The use of
getInitialsfor 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
openUrlfrom the Tauri plugin-opener for cross-platform URL opening and the nativenavigator.clipboardAPI 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_linkexists.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
mainViewHeightensures consistent panel dimensions during view switching, and the nestedAnimatePresencewithmode="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 onlyapps/desktop/src/components/main/body/contacts/details.tsx (1)
195-205: LGTM — neutral token migration looks consistentPresentational-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 solidConditional 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 LGTMReplaces nested button with icon inside trigger. Good.
apps/desktop/src/components/main/body/contacts/organizations.tsx (1)
89-97: Reverse‑alphabetical sorting implemented correctlyDescending name query plus switch case is correct.
Also applies to: 114-120
c0aeb59 to
7298341
Compare
7298341 to
12e7697
Compare
There was a problem hiding this 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 guidelineImport 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 passundefinedto hide the button.apps/desktop/src/components/main/body/contacts/organizations.tsx (1)
1-1: Align cn import with repo guidelineImport 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: Importcnfrom the correct location per coding guidelines.The
cnutility 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: Wrapcnarguments 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 itEnsure
useSortedHumanIdsanduseSortedOrganizationIdshandle"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 -C3Also applies to: 40-42
5-5: Import React event types explicitly (react-jsx) to prevent TS namespace errorsUse 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
📒 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 thecnutility imported asimport { cn } from "@hypr/ui/lib/utils"
When usingcn, always pass an array of class segments (e.g.,cn([ ... ]))
When usingcn, split the array by logical grouping of classes for readability
Files:
apps/desktop/src/components/main/body/contacts/organizations.tsxapps/desktop/src/components/main/sidebar/index.tsxapps/desktop/src/components/main/body/contacts/people.tsxapps/desktop/src/components/main/body/calendars.tsxapps/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.tsxapps/desktop/src/components/main/body/contacts/people.tsxapps/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 valuesThe nullish coalescing operator (
??) on lines 30-31 properly prevents TypeErrors whennameor
62-62: Good: Export enables cross-component reuseMaking
useSortedHumanIdspublic allows other components to leverage the sorted list, as indicated in the PR summary.
73-118: Reverse-alphabetical sorting implemented correctlyThe 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 valuesThe nullish coalescing operator (
??) on line 31 properly prevents TypeErrors whennameis undefined. Well done fixing the previous issue.
12-16: Detail view state properly controlledThe
isViewingOrgDetailsprop 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 correctlyThe 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-30wrapper creates a stacking context for the entire ProfileSection component, ensuring its absolutely-positioned dropdown menu (which expands upward withbottom-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
relativeon 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
useStoreand then usesstore.getRowwithin the filter callback. This does not violate the Rules of Hooks sincegetRowis a regular method call, not a hook.
354-362: LGTM! Date validation properly guards against invalid dates.Both
formatEventTimeandformatSessionTimecorrectly validate dates usingisNaN()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, andeachDayOfIntervalcorrectly generates all display dates.
130-154: LGTM! ButtonGroup enhances calendar navigation UX.The navigation controls are properly grouped using the
ButtonGroupcomponent, 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 SelectTriggerAvoids button-in-button and improves a11y. Looks right.