Skip to content

Add banner and sign-in features with calendar adjustments#1594

Merged
yujonglee merged 12 commits intomainfrom
jj-branch-26
Oct 23, 2025
Merged

Add banner and sign-in features with calendar adjustments#1594
yujonglee merged 12 commits intomainfrom
jj-branch-26

Conversation

@ComputelessComputer
Copy link
Collaborator

• Added banner component
• Implemented sign-in functionality
• Added logout feature
• Adjusted calendar height

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Oct 22, 2025

📝 Walkthrough

Walkthrough

This PR introduces keyboard navigation shortcuts for tabs, implements interactive billing UI, refactors settings components to use internal state, and extends tab components with a tabIndex prop for accessibility. Changes span calendar refinements, profile authentication UI, search shortcuts, and tab system updates.

Changes

Cohort / File(s) Summary
Tab index propagation infrastructure
apps/desktop/src/components/main/body/shared.tsx, apps/desktop/src/components/main/body/index.tsx
Extends TabItemProps and TabItemBaseProps with optional tabIndex?: number; computes keyboard shortcut indices (1–8 for first tabs, 9 for last); passes tabIndex through tab hierarchy; renders keyboard shortcut badge when Cmd key is pressed
Tab item components with tabIndex
apps/desktop/src/components/main/body/calendars.tsx, apps/desktop/src/components/main/body/contacts/index.tsx, apps/desktop/src/components/main/body/events.tsx, apps/desktop/src/components/main/body/folders.tsx, apps/desktop/src/components/main/body/humans.tsx, apps/desktop/src/components/main/body/sessions/index.tsx
Adds tabIndex prop to each TabItem* component and forwards it to underlying TabItemBase; enables keyboard shortcut display
Calendar refinements
apps/desktop/src/components/main/body/calendars.tsx
Introduces dynamic height measurement for day cells via cellRef and contentRef; adds maxVisibleItems state computed on mount and window resize; replaces static visibility calculations with dynamic logic based on available height
Keyboard shortcut infrastructure
apps/desktop/src/hooks/useCmdKeyPressed.tsx, apps/desktop/src/components/main/body/search.tsx
New hook useCmdKeyPressed tracks CMD/Meta key state; search component displays keyboard shortcut hint (⌘ K) when Cmd pressed and no query; adjusts input padding to accommodate shortcut badge
Profile sidebar
apps/desktop/src/components/main/sidebar/profile/index.tsx
Adds mock authentication state and banner dismissal logic; introduces handleAuth for sign-in/sign-out and handleClickBilling to open billing window; extends ProfileButton with onClickBilling prop; conditionally renders Log out or Sign in button and TryProBanner based on auth state; adds "My Profile" menu item
Settings integrations
apps/desktop/src/components/settings/integrations.tsx
Introduces FilterStatus state and ButtonGroup UI with All/Connected/Not Connected filter buttons; refactors filtering logic to apply status filter first, then search filter; updates styling (cursor-default on title, shadow-none on inputs)
Settings templates
apps/desktop/src/components/settings/template/index.tsx
Removes external navigate prop; introduces internal currentTemplate state and useTemplates hook for fetching user-specific templates; replaces navigation-based editor opening with local state; adds no-results UI flow; updates TemplateCard to trigger state-based editor
Billing settings
apps/desktop/src/components/settings/billing.tsx
Replaces null return with full interactive billing UI; introduces PLANS dataset, currentPlan state, and internal BillingPlanCard and PlanActions components; renders two-column plan layout with plan-specific action buttons (Current Plan, Downgrade, Upgrade, Contact Us)

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant SearchUI as Search Component
    participant CmdHook as useCmdKeyPressed
    participant ShortcutBadge as Shared/Tab Badge
    
    User->>CmdHook: Press Cmd key
    CmdHook->>CmdHook: Set isCmdPressed = true
    SearchUI->>CmdHook: Read isCmdPressed state
    SearchUI->>SearchUI: Compute showShortcut = isCmdPressed && !query
    SearchUI->>User: Display ⌘ K shortcut hint
    
    User->>ShortcutBadge: Cmd pressed, viewing tab
    ShortcutBadge->>ShortcutBadge: Compute showShortcut = isCmdPressed && tabIndex !== undefined
    ShortcutBadge->>User: Display ⌘ {tabIndex} badge
    
    User->>CmdHook: Release Cmd key
    CmdHook->>CmdHook: Set isCmdPressed = false
    SearchUI->>User: Hide ⌘ K shortcut
    ShortcutBadge->>User: Hide ⌘ {tabIndex} badge
Loading
sequenceDiagram
    participant User
    participant ProfileUI as Profile Sidebar
    participant AuthState as Authentication State
    participant WindowAPI as Windows Plugin
    
    User->>ProfileUI: Click on Profile button
    ProfileUI->>AuthState: Check isAuthenticated
    
    alt Not Authenticated
        AuthState-->>ProfileUI: Return false
        ProfileUI->>User: Show Sign in button + TryProBanner
        User->>ProfileUI: Click Sign in
        ProfileUI->>AuthState: handleAuth() - set isAuthenticated = true
    else Authenticated
        AuthState-->>ProfileUI: Return true
        ProfileUI->>User: Show Log out button
        User->>ProfileUI: Click Log out
        ProfileUI->>AuthState: handleAuth() - set isAuthenticated = false
    end
    
    User->>ProfileUI: Click Billing (from Pro trial badge)
    ProfileUI->>WindowAPI: handleClickBilling()
    WindowAPI->>WindowAPI: Open billing window
    WindowAPI->>ProfileUI: Navigate to billing tab
Loading
sequenceDiagram
    participant User
    participant TemplatesUI as Settings Templates
    participant LocalState as currentTemplate State
    participant TemplateEditor as TemplateEditor Component
    participant useTemplates as useTemplates Hook
    
    TemplatesUI->>useTemplates: Fetch user templates
    useTemplates-->>TemplatesUI: Return templates array
    TemplatesUI->>TemplatesUI: Filter by searchQuery
    TemplatesUI->>User: Display filtered template cards
    
    User->>TemplatesUI: Click on template card
    TemplatesUI->>LocalState: Set currentTemplate = selected
    LocalState-->>TemplatesUI: Update state
    TemplatesUI->>TemplateEditor: Render with currentTemplate
    TemplateEditor->>User: Display editor for template
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Rationale: The PR introduces multiple cohesive but distinct features: (1) repetitive tabIndex prop propagation across six tab components (lower complexity, higher repetition), (2) new keyboard shortcut infrastructure with hook and UI integration (moderate complexity), (3) profile authentication/billing state management (moderate complexity, moderate scope), (4) settings refactoring with internal state and hooks (moderate complexity), and (5) new billing UI implementation (moderate complexity, self-contained). The breadth spans many files but changes are well-compartmentalized; repetitive patterns reduce per-file cognitive load, though the variety of changes requires separate reasoning for different cohorts.

Possibly related PRs

  • Calendar card 1010 #1551: Directly related—both modify calendar day rendering in calendars.tsx to introduce dynamic maxVisibleItems logic and item visibility handling.
  • template setting styling #1592: Directly related—both modify settings/templates and settings/integrations files with template editor navigation changes and FilterStatus UI additions respectively.
  • General settings improvements #1587: Related—both modify apps/desktop/src/components/main/body/index.tsx and alter tab/header behavior (this PR adds tabIndex propagation for keyboard shortcuts; the other centralizes hotkey and new-tab logic).

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Title Check ✅ Passed The pull request title "Add banner and sign-in features with calendar adjustments" refers to real, present aspects of the changeset: the profile component does include banner state and sign-in/logout functionality, and the calendars component includes dynamic height measurement adjustments. However, the changeset includes substantial additional work that is not captured by the title, including a comprehensive keyboard shortcut system (new useCmdKeyPressed hook, tabIndex prop threading across six tab components, keyboard shortcut UI rendering), billing UI implementation, settings integrations filtering, and template editor refactoring. While the title is accurate regarding what it describes, it significantly underrepresents the full scope of changes in the pull request.
Description Check ✅ Passed The pull request description lists four specific, related aspects: banner component, sign-in functionality, logout feature, and calendar height adjustments. All four of these items correspond to real changes present in the changeset, and the description is not vague or generic in its phrasing. Following the lenient evaluation criteria for descriptions, where level of detail is not important as long as the description is related to the changeset, this description clearly meets the pass threshold.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch jj-branch-26

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

❤️ Share

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

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

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

252-274: Consider extracting magic numbers as named constants.

The implementation correctly measures available height and calculates visible items. However, the hardcoded values EVENT_HEIGHT = 20 and SPACING = 4 could become maintenance issues if the corresponding Tailwind classes (h-5 at line 391, space-y-1 at line 328) are changed.

Consider extracting these at the module level:

const CALENDAR_ITEM_HEIGHT = 20; // matches h-5 class
const CALENDAR_ITEM_SPACING = 4; // matches space-y-1 class

Then reference them in both the CSS classes (via comments) and the calculation:

const EVENT_HEIGHT = CALENDAR_ITEM_HEIGHT;
const SPACING = CALENDAR_ITEM_SPACING;
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between cf59e63 and a3d92cf.

📒 Files selected for processing (2)
  • apps/desktop/src/components/main/body/calendars.tsx (6 hunks)
  • apps/desktop/src/components/main/sidebar/profile/index.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 Tailwind classNames with conditional logic, use the utility cn imported as import { cn } from "@hypr/utils"
When using cn for Tailwind classNames, always pass an array
Group Tailwind classNames by logical sections when using cn (split array items by grouping)

Files:

  • apps/desktop/src/components/main/sidebar/profile/index.tsx
  • apps/desktop/src/components/main/body/calendars.tsx
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: ci (macos, macos-14)
🔇 Additional comments (8)
apps/desktop/src/components/main/body/calendars.tsx (4)

19-19: LGTM: Necessary imports for dynamic height measurement.

The addition of useEffect and useRef supports the new dynamic height calculation feature implemented below.


141-141: LGTM: UI refinement.

The size adjustment to "sm" improves the visual balance of the Today button within the button group.


224-226: LGTM: Proper ref and state setup.

The refs and state are correctly initialized for measuring available height and calculating the maximum number of visible items dynamically.


277-279: LGTM: Dynamic visible count calculation and ref placement.

The visible count calculation correctly uses the dynamic maxVisibleItems and reserves space for the "+x more" indicator when needed. The refs are applied to the appropriate DOM elements for accurate height measurements.

Also applies to: 299-299, 328-328

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

2-2: LGTM: Required imports for authentication UI.

The Button component and authentication-related icons are properly imported to support the sign-in/logout functionality.

Also applies to: 6-6


26-28: LGTM: Clear mock authentication state.

The authentication state is clearly documented as mock implementation, with sensible initial values.


38-50: LGTM: Correct authentication handler logic.

The function properly toggles authentication state and resets banner dismissal on logout, ensuring the banner reappears for new sign-in flows. The console logs are acceptable for mock implementation.


95-95: LGTM: Complete dependency array.

The inclusion of both isBannerDismissed and isAuthenticated is correct, as both affect the main view height and require recalculation when changed.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

♻️ Duplicate comments (1)
apps/desktop/src/components/main/sidebar/profile/index.tsx (1)

202-228: Fix: Exit animation broken by conditional rendering.

The banner's exit animation won't play because the parent unmounts TryProBanner immediately when isBannerDismissed becomes true. Line 216 also hardcodes isDismissed={false} instead of passing the actual state.

Apply this diff to fix the animation:

                        {menuItems.map((item) => <MenuItem key={item.label} {...item} />)}

                        {isAuthenticated
                          ? (
                            <div className="px-1 py-2">
                              <Button
                                onClick={handleAuth}
                                variant="outline"
                                className="w-full"
                              >
                                <LogOut className="w-4 h-4 mr-2" />
                                Log out
                              </Button>
                            </div>
                          )
-                         : !isBannerDismissed
-                         ? <TryProBanner isDismissed={false} onDismiss={handleDismissBanner} />
-                         : (
+                         : (
+                           <>
+                             <TryProBanner isDismissed={isBannerDismissed} onDismiss={handleDismissBanner} />
+                             {isBannerDismissed && (
-                           <div className="px-1 py-2">
-                             <Button
-                               onClick={handleAuth}
-                               variant="default"
-                               className="w-full"
-                             >
-                               <LogIn className="w-4 h-4 mr-2" />
-                               Sign in
-                             </Button>
-                           </div>
+                               <div className="px-1 py-2">
+                                 <Button
+                                   onClick={handleAuth}
+                                   variant="default"
+                                   className="w-full"
+                                 >
+                                   <LogIn className="w-4 h-4 mr-2" />
+                                   Sign in
+                                 </Button>
+                               </div>
+                             )}
+                           </>
                          )}
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a3d92cf and 4751264.

📒 Files selected for processing (4)
  • apps/desktop/src/components/main/sidebar/profile/index.tsx (9 hunks)
  • apps/desktop/src/components/settings/integrations.tsx (3 hunks)
  • apps/desktop/src/components/settings/template/index.tsx (2 hunks)
  • apps/desktop/src/routes/app/settings/_layout.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 Tailwind classNames with conditional logic, use the utility cn imported as import { cn } from "@hypr/utils"
When using cn for Tailwind classNames, always pass an array
Group Tailwind classNames by logical sections when using cn (split array items by grouping)

Files:

  • apps/desktop/src/components/settings/template/index.tsx
  • apps/desktop/src/routes/app/settings/_layout.tsx
  • apps/desktop/src/components/main/sidebar/profile/index.tsx
  • apps/desktop/src/components/settings/integrations.tsx
🧬 Code graph analysis (3)
apps/desktop/src/components/settings/template/index.tsx (2)
packages/db/src/schema.ts (1)
  • templates (101-106)
apps/desktop/src/components/settings/template/editor.tsx (1)
  • TemplateEditor (20-209)
apps/desktop/src/components/main/sidebar/profile/index.tsx (1)
apps/desktop/src/components/main/sidebar/profile/banner.tsx (1)
  • TryProBanner (7-70)
apps/desktop/src/components/settings/integrations.tsx (1)
apps/desktop/src/components/main/body/search.tsx (1)
  • Search (7-62)
🪛 Biome (2.1.2)
apps/desktop/src/components/settings/template/index.tsx

[error] 29-29: filtered is assigned to itself.

This is where is assigned.

Self assignments have no effect and can be removed.

(lint/correctness/noSelfAssign)

⏰ 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 (9)
apps/desktop/src/routes/app/settings/_layout.tsx (1)

64-64: LGTM!

The scrollbar-none class appropriately hides the scrollbar for a cleaner settings layout.

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

86-114: LGTM!

The filtering logic is well-structured with clear separation between status and search filters. The memoization includes all necessary dependencies.


131-156: LGTM!

The ButtonGroup implementation provides a clean UI for status filtering and follows consistent patterns.

apps/desktop/src/components/settings/template/index.tsx (2)

32-43: LGTM!

The search filtering logic is well-implemented with proper memoization and dependency tracking.


54-106: LGTM!

The filter UI and template rendering logic follow consistent patterns and provide a clean user experience with appropriate empty states.

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

26-28: Reminder: Mock authentication state for testing.

The mock isAuthenticated state should be replaced with actual authentication logic before production deployment.

Are you planning to integrate real authentication in a follow-up PR?


38-50: LGTM!

The authentication handler correctly manages state transitions and resets the banner on logout.


141-151: LGTM!

The profile click handler opens the contacts view appropriately. The TODO comment correctly notes that this should eventually show the user's own profile.


261-263: LGTM!

The onClickBilling prop integration is well-implemented with proper event propagation handling on the Pro trial badge.

Also applies to: 297-310

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

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

⚠️ Outside diff range comments (1)
apps/desktop/src/components/main/body/sessions/floating/listen.tsx (1)

144-151: Complete useRemoteMeeting to detect meeting platform from session data.

The function is indeed a stub—it ignores the _sessionId parameter (indicated by the underscore prefix) and always returns google-meet. However, the caller in BeforeMeeingButton expects detection of four meeting types (zoom, google-meet, webex, teams). The function must be implemented to determine the actual meeting platform based on the session ID and return the corresponding type.

🧹 Nitpick comments (3)
apps/desktop/src/components/main/body/sessions/floating/listen.tsx (1)

126-126: Apply the same size prop pattern for consistency.

This Icon still uses className-based sizing (w-5 h-5) while the other Icon components in this file now use the size prop. For consistency, use size={20} and keep className only for the margin.

Apply this diff:

-      icon={hovered ? <Icon icon="lucide:stop-circle" className="w-5 h-5 mt-1.5" /> : undefined}
+      icon={hovered ? <Icon icon="lucide:stop-circle" size={20} className="mt-1.5" /> : undefined}
apps/desktop/src/hooks/useCmdKeyPressed.tsx (1)

17-21: Optimize handleKeyUp to prevent unnecessary state updates.

The condition !e.metaKey || e.key === "Meta" will trigger setIsCmdPressed(false) on every key release when Cmd is not held (e.g., typing normal text). This causes unnecessary re-renders across all components using this hook.

Apply this diff to only reset state when the Meta key itself is released:

     const handleKeyUp = (e: KeyboardEvent) => {
-      if (!e.metaKey || e.key === "Meta") {
+      if (e.key === "Meta") {
         setIsCmdPressed(false);
       }
     };

The handleBlur already covers the case where the window loses focus while Cmd is held.

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

226-228: Excellent dynamic height calculation implementation!

The per-day height measurement and dynamic maxVisibleItems calculation is a solid improvement over static limits. The implementation correctly:

  • Measures actual DOM dimensions using refs
  • Calculates available space and fits items accordingly
  • Reserves space for the "+x more" indicator when needed
  • Cleans up the resize listener properly
  • Uses Math.max(1, ...) to ensure at least one item is always visible

Optional: Extract magic numbers as named constants.

The values EVENT_HEIGHT = 20 and SPACING = 4 are correct (matching h-5 and space-y-1 Tailwind classes), but they're tightly coupled to the CSS. Consider extracting these as module-level constants for better maintainability:

+// Calendar day cell item dimensions
+const EVENT_ITEM_HEIGHT = 20; // matches h-5 (1.25rem)
+const EVENT_ITEM_SPACING = 4; // matches space-y-1 (0.25rem)
+
 function TabContentCalendarDay({
   // ...
 }) {
   // ...
   useEffect(() => {
     const measureHeight = () => {
       if (cellRef.current && contentRef.current) {
         const cellHeight = cellRef.current.clientHeight;
         const contentTop = contentRef.current.offsetTop;
         const availableHeight = cellHeight - contentTop;
-        const EVENT_HEIGHT = 20; // height of each event item (h-5)
-        const SPACING = 4; // space-y-1
         
         // Calculate how many items can fit
-        const itemsWithSpacing = Math.floor((availableHeight + SPACING) / (EVENT_HEIGHT + SPACING));
+        const itemsWithSpacing = Math.floor((availableHeight + EVENT_ITEM_SPACING) / (EVENT_ITEM_HEIGHT + EVENT_ITEM_SPACING));
         // Reserve space for "+x more" if needed
         setMaxVisibleItems(Math.max(1, itemsWithSpacing));
       }
     };

This makes it easier to update if the CSS classes change and improves code clarity.

Also applies to: 254-276, 279-281, 301-301, 330-330

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 4751264 and efd7cb8.

📒 Files selected for processing (11)
  • apps/desktop/src/components/main/body/calendars.tsx (8 hunks)
  • apps/desktop/src/components/main/body/contacts/index.tsx (2 hunks)
  • apps/desktop/src/components/main/body/events.tsx (2 hunks)
  • apps/desktop/src/components/main/body/folders.tsx (4 hunks)
  • apps/desktop/src/components/main/body/humans.tsx (2 hunks)
  • apps/desktop/src/components/main/body/index.tsx (8 hunks)
  • apps/desktop/src/components/main/body/search.tsx (3 hunks)
  • apps/desktop/src/components/main/body/sessions/floating/listen.tsx (4 hunks)
  • apps/desktop/src/components/main/body/sessions/index.tsx (2 hunks)
  • apps/desktop/src/components/main/body/shared.tsx (4 hunks)
  • apps/desktop/src/hooks/useCmdKeyPressed.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 Tailwind classNames with conditional logic, use the utility cn imported as import { cn } from "@hypr/utils"
When using cn for Tailwind classNames, always pass an array
Group Tailwind classNames by logical sections when using cn (split array items by grouping)

Files:

  • apps/desktop/src/components/main/body/events.tsx
  • apps/desktop/src/components/main/body/contacts/index.tsx
  • apps/desktop/src/components/main/body/humans.tsx
  • apps/desktop/src/components/main/body/search.tsx
  • apps/desktop/src/components/main/body/sessions/index.tsx
  • apps/desktop/src/hooks/useCmdKeyPressed.tsx
  • apps/desktop/src/components/main/body/sessions/floating/listen.tsx
  • apps/desktop/src/components/main/body/index.tsx
  • apps/desktop/src/components/main/body/calendars.tsx
  • apps/desktop/src/components/main/body/folders.tsx
  • apps/desktop/src/components/main/body/shared.tsx
🧬 Code graph analysis (3)
apps/desktop/src/components/main/body/search.tsx (2)
apps/desktop/src/contexts/search/ui.tsx (1)
  • useSearch (219-225)
apps/desktop/src/hooks/useCmdKeyPressed.tsx (1)
  • useCmdKeyPressed (7-40)
apps/desktop/src/components/main/body/index.tsx (2)
apps/desktop/src/store/zustand/tabs/schema.ts (2)
  • uniqueIdfromTab (86-103)
  • Tab (54-54)
apps/desktop/src/components/main/body/shared.tsx (1)
  • TabItem (26-26)
apps/desktop/src/components/main/body/shared.tsx (2)
apps/desktop/src/hooks/useCmdKeyPressed.tsx (1)
  • useCmdKeyPressed (7-40)
packages/ui/src/components/ui/kbd.tsx (2)
  • KbdGroup (28-28)
  • Kbd (28-28)
⏰ 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 (13)
apps/desktop/src/components/main/body/sessions/floating/listen.tsx (1)

54-54: LGTM! Icon sizing is now consistent.

Using the size prop instead of Tailwind className-based sizing (w-5 h-5) improves consistency and leverages the Icon component's dedicated sizing API.

Also applies to: 65-65, 76-76, 87-87

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

12-70: LGTM! Clean keyboard shortcut integration.

The implementation correctly:

  • Tracks Cmd key state via the hook
  • Dynamically adjusts input padding to prevent overlap with the shortcut badge and clear button
  • Shows the ⌘ K hint only when appropriate (Cmd pressed and no query)
  • Follows coding guidelines by using cn with logically grouped arrays
apps/desktop/src/components/main/body/shared.tsx (1)

12-100: Keyboard shortcut feature implementation looks solid.

The tabIndex prop threading and conditional shortcut badge rendering are well-implemented:

  • Clean prop typing with optional tabIndex
  • Efficient conditional rendering based on Cmd key state
  • Good use of pointer-events-none to prevent interaction interference
  • Appropriate positioning for the shortcut badge
apps/desktop/src/components/main/body/contacts/index.tsx (1)

18-30: LGTM! Consistent tabIndex prop threading.

The tabIndex prop is correctly added to the component signature and forwarded to TabItemBase, consistent with the pattern used across all TabItem variants in this PR.

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

19-33: LGTM! Consistent tabIndex prop threading.

The tabIndex prop is correctly added to the component signature and forwarded to TabItemBase, consistent with the pattern used across all TabItem variants in this PR.

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

11-25: LGTM! Consistent tabIndex prop threading.

The tabIndex prop is correctly added to the component signature and forwarded to TabItemBase, consistent with the pattern used across all TabItem variants in this PR.

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

25-37: LGTM! Consistent tabIndex prop threading in TabItemFolderAll.

The tabIndex prop is correctly added to the component signature and forwarded to TabItemBase, consistent with the pattern used across all TabItem variants in this PR.


48-65: LGTM! Consistent tabIndex prop threading in TabItemFolderSpecific.

The tabIndex prop is correctly added to the component signature and forwarded to TabItemBase, consistent with the pattern used across all TabItem variants in this PR.

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

10-27: LGTM! Consistent tabIndex prop threading.

The tabIndex prop is correctly added to the component signature and forwarded to TabItemBase, consistent with the pattern used across all TabItem variants in this PR.

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

26-48: LGTM! Keyboard navigation support added.

The tabIndex prop is properly accepted and forwarded to TabItemBase, enabling keyboard shortcuts for calendar tabs. This aligns with the broader tabIndex propagation pattern implemented across all tab components.


143-143: LGTM! Button size adjustment improves visual consistency.

Using size="sm" provides better visual balance in the header alongside the icon buttons.

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

96-120: LGTM! Keyboard shortcut indices correctly calculated.

The shortcutIndex logic properly maps:

  • First 8 tabs → Cmd+1 through Cmd+8
  • Last tab → Cmd+9 (when beyond the first 8)
  • Intermediate tabs → no shortcut (undefined)

This aligns perfectly with the keyboard shortcut handler in useTabsShortcuts (lines 352-364).


143-234: LGTM! Consistent tabIndex propagation across all tab types.

The TabItem function properly accepts the optional tabIndex parameter and forwards it to all six tab variants (sessions, events, folders, humans, calendars, contacts). This ensures uniform keyboard navigation support across the entire tab system.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between efd7cb8 and 8b7c7fb.

📒 Files selected for processing (1)
  • apps/desktop/src/components/settings/billing.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 Tailwind classNames with conditional logic, use the utility cn imported as import { cn } from "@hypr/utils"
When using cn for Tailwind classNames, always pass an array
Group Tailwind classNames by logical sections when using cn (split array items by grouping)

Files:

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

59-59: Verify if the hardcoded initial plan is intentional.

The current plan is hardcoded to "free". In a production app, this should typically be fetched from user data or an API.


70-77: Verify stub implementations for plan changes and contact.

Both handlers only perform local actions (console logging and opening a URL) without backend integration or error handling. Confirm whether:

  • Plan changes should trigger API calls or state persistence
  • URL opening failures should provide user feedback

176-228: LGTM!

The conditional logic correctly handles all plan state transitions with appropriate button labels and variants.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

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

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

269-324: Use arrays with cn() utility as per coding guidelines.

The coding guidelines specify: "When using cn for Tailwind classNames, always pass an array" and "Group Tailwind classNames by logical sections when using cn (split array items by grouping)". All four cn() calls should be updated to use arrays.

As per coding guidelines

Apply this diff:

     <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",
+      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",
+      ])}
-      )}
       onClick={onClick}
     >
       <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",
-          "bg-gradient-to-br from-indigo-400 to-purple-500",
-          "shadow-sm",
-          "transition-transform duration-300",
+        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",
+        ])}
-        )}
       >
         <img
           src="https://api.dicebear.com/7.x/avataaars/svg?seed=JohnJeong"
           alt="Profile"
           className="h-full w-full rounded-full"
         />
       </div>
       <div className="min-w-0 flex-1">
         <div className="text-sm text-black truncate">John Jeong</div>
       </div>
       <div className="flex items-center gap-1.5">
         <span
           onClick={(e) => {
             e.stopPropagation();
             onClickBilling();
           }}
-          className={cn(
-            "hidden md:inline-block",
-            "rounded-full",
-            "border border-neutral-900",
-            "bg-white",
-            "px-2.5 py-0.5",
-            "text-[11px] font-medium text-neutral-900",
-            "cursor-pointer",
-            "hover:bg-neutral-100",
-            "transition-colors duration-200",
+          className={cn([
+            "hidden md:inline-block",
+            "rounded-full",
+            "border border-neutral-900",
+            "bg-white",
+            "px-2.5 py-0.5",
+            "text-[11px] font-medium text-neutral-900",
+            "cursor-pointer",
+            "hover:bg-neutral-100",
+            "transition-colors duration-200",
+          ])}
-          )}
         >
           Pro trial
         </span>
         <ChevronUpIcon
-          className={cn(
-            "h-4 w-4",
-            "transition-transform duration-300",
-            isExpanded ? "rotate-180 text-neutral-500" : "text-neutral-400",
+          className={cn([
+            "h-4 w-4",
+            "transition-transform duration-300",
+            isExpanded ? "rotate-180 text-neutral-500" : "text-neutral-400",
+          ])}
-          )}
         />
       </div>
     </button>
♻️ Duplicate comments (3)
apps/desktop/src/components/main/sidebar/profile/index.tsx (1)

104-110: Move closeMenu() into the promise chain.

This is the same timing issue flagged in the previous review. The menu closes before navigation completes because closeMenu() executes synchronously. Move it into the .then() callback:

 const handleClickBilling = useCallback(() => {
   windowsCommands.windowShow({ type: "settings" }).then(() => {
     windowsCommands.windowEmitNavigate({ type: "settings" }, { path: "/app/settings", search: { tab: "billing" } });
+    closeMenu();
   });
-  closeMenu();
 }, [closeMenu]);
apps/desktop/src/components/settings/billing.tsx (2)

137-144: Group Tailwind classes by logical sections within cn.

You switched to an array (good), but background is mixed with layout. Group for clarity and consistency.

As per coding guidelines.

-      className={cn(
-        [
-          "p-6 flex flex-col gap-6 bg-white",
-          !removeBorder && "border border-neutral-200 rounded-lg",
-          plan.id === "ultimate" && "bg-neutral-50",
-          className,
-        ],
-      )}
+      className={cn([
+        // Layout & spacing
+        "p-6 flex flex-col gap-6",
+        // Background
+        "bg-white",
+        plan.id === "ultimate" && "bg-neutral-50",
+        // Border
+        !removeBorder && "border border-neutral-200 rounded-lg",
+        // Custom overrides
+        className,
+      ])}

90-90: Pass an array to cn.

cn should always receive a single array. Wrap the conditional class in an array.

As per coding guidelines.

-                className={cn(index === 0 && "border-r border-neutral-200")}
+                className={cn([index === 0 && "border-r border-neutral-200"])}
🧹 Nitpick comments (4)
apps/desktop/src/components/settings/template/index.tsx (1)

140-140: Fix typo in variable name.

The variable is named quries instead of queries. While this is pre-existing code, it's a simple fix that improves readability.

Apply this diff:

-  const quries = persisted.UI.useCreateQueries(
+  const queries = persisted.UI.useCreateQueries(

And update line 152:

-  const templates = persisted.UI.useResultTable(USER_TEMPLATE_QUERY, quries);
+  const templates = persisted.UI.useResultTable(USER_TEMPLATE_QUERY, queries);
apps/desktop/src/components/main/sidebar/profile/index.tsx (1)

141-151: Address TODO: Show user's own profile.

The TODO indicates that the current implementation opens the contacts view but doesn't specifically show the user's own profile. Consider implementing logic to select/highlight the current user's profile when this action is triggered.

Would you like me to help generate the implementation that pre-selects the user's profile in the contacts view?

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

61-68: Avoid useMemo for constants; hoist derived plans.

Derived values from a file‑level constant don’t need useMemo([]). Hoist once and reference directly; also removes the non‑null assertion in render paths.

Apply inside component:

-  const standardPlans = useMemo(
-    () => PLANS.filter((plan) => plan.id !== "ultimate"),
-    [],
-  );
-  const ultimatePlan = useMemo(
-    () => PLANS.find((plan) => plan.id === "ultimate")!,
-    [],
-  );
+  const standardPlans = STANDARD_PLANS;
+  const ultimatePlan = ULTIMATE_PLAN;

Add near PLANS (file scope):

const STANDARD_PLANS: BillingPlan[] = PLANS.filter((p) => p.id !== "ultimate");
const ULTIMATE_PLAN = PLANS.find((p) => p.id === "ultimate")!;

152-158: Mark decorative icons aria-hidden.

The checkmark is decorative; hide it from screen readers.

-              <Check size={16} className="mt-0.5 text-emerald-500 shrink-0" />
+              <Check size={16} aria-hidden="true" focusable="false" className="mt-0.5 text-emerald-500 shrink-0" />
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 8b7c7fb and 9d30560.

📒 Files selected for processing (4)
  • apps/desktop/src/components/main/body/shared.tsx (5 hunks)
  • apps/desktop/src/components/main/sidebar/profile/index.tsx (10 hunks)
  • apps/desktop/src/components/settings/billing.tsx (1 hunks)
  • apps/desktop/src/components/settings/template/index.tsx (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • apps/desktop/src/components/main/body/shared.tsx
🧰 Additional context used
📓 Path-based instructions (1)
apps/desktop/**/*.{tsx,jsx}

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

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

Files:

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

Applied to files:

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

Applied to files:

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

Applied to files:

  • apps/desktop/src/components/settings/billing.tsx
🧬 Code graph analysis (3)
apps/desktop/src/components/main/sidebar/profile/index.tsx (2)
apps/desktop/src/components/main/sidebar/profile/banner.tsx (1)
  • TryProBanner (7-70)
packages/utils/src/cn.ts (1)
  • cn (20-22)
apps/desktop/src/components/settings/template/index.tsx (2)
packages/db/src/schema.ts (1)
  • templates (101-106)
apps/desktop/src/components/settings/template/editor.tsx (1)
  • TemplateEditor (20-209)
apps/desktop/src/components/settings/billing.tsx (2)
packages/utils/src/cn.ts (1)
  • cn (20-22)
packages/ui/src/components/ui/button.tsx (1)
  • Button (53-53)
🔇 Additional comments (9)
apps/desktop/src/components/settings/template/index.tsx (2)

26-27: Past issue resolved.

The no-op self-assignment flagged in the previous review has been correctly removed. Only the TODO comment remains, which is appropriate.


120-124: Excellent adherence to coding guidelines.

The cn usage correctly follows the guidelines by using array format and grouping Tailwind classes by logical sections (layout, interaction, and visual styling). The change from items-center to items-start also better accommodates multi-line descriptions.

As per coding guidelines

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

2-6: LGTM!

The import additions are appropriate and all imported items are used in the component.


26-28: Replace mock authentication with real auth integration.

The mock authentication state is suitable for testing but should be replaced with actual authentication logic (e.g., from an auth provider or service) before production deployment.


38-50: Replace mock auth handlers with real authentication.

The console.log statements should be replaced with actual authentication calls (sign-in/sign-out). The logic for resetting the banner on logout is appropriate.


95-95: LGTM!

Correctly includes isBannerDismissed and isAuthenticated dependencies, as changes to these states affect the rendered content height.


202-231: LGTM! Exit animation fix has been applied correctly.

The banner rendering structure now matches the suggested fix from the previous review. The TryProBanner is always rendered when the user is not authenticated and receives the isDismissed prop, allowing its internal AnimatePresence exit animation to play correctly before the sign-in button appears.


254-258: LGTM!

The ProfileButton usage correctly passes the onClickBilling handler, enabling the billing navigation feature.

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

178-230: PlanActions logic reads clean and covers all states.

Early return for current plan and clear upgrade/downgrade labels look good.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

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/settings/template/index.tsx (1)

140-140: Fix typo in variable name.

The variable name quries should be queries.

Apply this diff:

-  const quries = persisted.UI.useCreateQueries(
+  const queries = persisted.UI.useCreateQueries(
     store,
     (store) =>
       createQueries(store).setQueryDefinition(USER_TEMPLATE_QUERY, "templates", ({ select, where }) => {
         select("title");
         select("description");
         select("sections");
         where("user_id", user_id ?? "");
       }),
     [user_id],
   );

-  const templates = persisted.UI.useResultTable(USER_TEMPLATE_QUERY, quries);
+  const templates = persisted.UI.useResultTable(USER_TEMPLATE_QUERY, queries);
   return templates as unknown as Record<string, persisted.Template>;
apps/desktop/src/components/main/sidebar/profile/index.tsx (3)

269-276: Pass an array to cn and group classNames per coding guidelines.

The coding guidelines require: "When using cn for Tailwind classNames, always pass an array" and "Group Tailwind classNames by logical sections".

As per coding guidelines.

Apply this diff:

-      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",
-      )}
+      className={cn([
+        // Layout
+        "flex w-full items-center gap-2.5",
+        "px-4 py-2",
+        "text-left",
+        // Interactions
+        "transition-all duration-300",
+        "hover:bg-neutral-100",
+        isExpanded && "bg-neutral-50 border-t border-neutral-100",
+      ])}

280-287: Pass an array to cn and group classNames per coding guidelines.

As per coding guidelines.

Apply this diff:

-        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",
-        )}
+        className={cn([
+          // Layout
+          "flex size-8 flex-shrink-0 items-center justify-center",
+          "overflow-hidden rounded-full",
+          // Styling
+          "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",
+        ])}

319-323: Pass an array to cn per coding guidelines.

As per coding guidelines.

Apply this diff:

-          className={cn(
-            "h-4 w-4",
-            "transition-transform duration-300",
-            isExpanded ? "rotate-180 text-neutral-500" : "text-neutral-400",
-          )}
+          className={cn([
+            "h-4 w-4",
+            "transition-transform duration-300",
+            isExpanded ? "rotate-180 text-neutral-500" : "text-neutral-400",
+          ])}
♻️ Duplicate comments (4)
apps/desktop/src/components/settings/billing.tsx (2)

90-90: Pass an array to cn per coding guidelines.

The coding guidelines specify: "When using cn for Tailwind classNames, always pass an array".

As per coding guidelines.


137-144: Group classNames by logical sections per coding guidelines.

While the array wrapper is now present, the coding guidelines also specify: "Group Tailwind classNames by logical sections when using cn (split array items by grouping)".

As per coding guidelines.

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

104-110: Move closeMenu() into the promise chain.

The TODO comment is valid—closeMenu() executes synchronously while the navigation is still pending, which can cause timing issues.


202-231: The TryProBanner animation handling appears correct.

The current implementation keeps TryProBanner mounted when not authenticated and passes the isDismissed prop, allowing the internal AnimatePresence to handle exit animations properly. This addresses the concern raised in previous reviews.

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 9d30560 and 98581e1.

📒 Files selected for processing (4)
  • apps/desktop/src/components/main/body/shared.tsx (5 hunks)
  • apps/desktop/src/components/main/sidebar/profile/index.tsx (10 hunks)
  • apps/desktop/src/components/settings/billing.tsx (1 hunks)
  • apps/desktop/src/components/settings/template/index.tsx (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • apps/desktop/src/components/main/body/shared.tsx
🧰 Additional context used
📓 Path-based instructions (1)
apps/desktop/**/*.{tsx,jsx}

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

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

Files:

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

Applied to files:

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

Applied to files:

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

Applied to files:

  • apps/desktop/src/components/settings/billing.tsx
🧬 Code graph analysis (3)
apps/desktop/src/components/settings/template/index.tsx (2)
packages/db/src/schema.ts (1)
  • templates (101-106)
apps/desktop/src/components/settings/template/editor.tsx (1)
  • TemplateEditor (20-209)
apps/desktop/src/components/settings/billing.tsx (1)
packages/utils/src/cn.ts (1)
  • cn (20-22)
apps/desktop/src/components/main/sidebar/profile/index.tsx (3)
packages/ui/src/components/ui/button.tsx (1)
  • Button (53-53)
apps/desktop/src/components/main/sidebar/profile/banner.tsx (1)
  • TryProBanner (7-70)
packages/utils/src/cn.ts (1)
  • cn (20-22)
🔇 Additional comments (2)
apps/desktop/src/components/settings/template/index.tsx (2)

23-39: LGTM! Past review issues have been addressed.

The filtering logic is correct:

  • The no-op self-assignment has been removed
  • filterStatus has been correctly removed from the dependency array
  • Search filtering properly checks both title and description fields with null coalescing

108-132: LGTM! Proper use of cn utility per coding guidelines.

The cn utility is used correctly with:

  • Array syntax as required
  • Logical grouping of Tailwind classes (layout, interaction, styling)

As per coding guidelines.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

♻️ Duplicate comments (2)
apps/desktop/src/components/settings/template/index.tsx (1)

49-67: Remove non-functional favorite filter UI or implement the feature.

The ButtonGroup with All/Favorite buttons is still present but has no effect on the filtered results. Clicking these buttons updates filterStatus state (line 19), but this state is never used in the filtering logic (lines 22-38). This creates a confusing user experience where the UI appears interactive but produces no results.

Either remove the entire ButtonGroup and the FilterStatus type definition (line 14) and filterStatus state (line 19), or implement the favorite filtering logic in the useMemo block.

Apply this diff to remove the non-functional UI:

-type FilterStatus = "all" | "favorite";
-
 export function SettingsTemplates() {
   const [currentTemplate, setCurrentTemplate] = useState<string | null>(null);
   const [searchQuery, setSearchQuery] = useState("");
-  const [filterStatus, setFilterStatus] = useState<FilterStatus>("all");
   const templates = useTemplates();

And remove the ButtonGroup from the JSX:

       <div className="flex items-center justify-between mb-4">
         <h2 className="font-semibold cursor-default">Templates</h2>
-        <ButtonGroup>
-          <Button
-            variant={filterStatus === "all" ? "default" : "outline"}
-            size="sm"
-            onClick={() => setFilterStatus("all")}
-            className="shadow-none"
-          >
-            All
-          </Button>
-          <Button
-            variant={filterStatus === "favorite" ? "default" : "outline"}
-            size="sm"
-            onClick={() => setFilterStatus("favorite")}
-            className="shadow-none"
-          >
-            Favorite
-          </Button>
-        </ButtonGroup>
       </div>

Also remove the unused ButtonGroup import:

 import { Button } from "@hypr/ui/components/ui/button";
-import { ButtonGroup } from "@hypr/ui/components/ui/button-group";
 import { Input } from "@hypr/ui/components/ui/input";
apps/desktop/src/components/settings/billing.tsx (1)

90-90: Pass an array to cn per coding guidelines.

This issue was flagged in a previous review but has not been addressed. The coding guidelines specify: "When using cn for Tailwind classNames, always pass an array".

As per coding guidelines.

Apply this diff:

-                className={cn(index === 0 && "border-r border-neutral-200")}
+                className={cn([index === 0 && "border-r border-neutral-200"])}
🧹 Nitpick comments (2)
apps/desktop/src/components/settings/template/index.tsx (1)

69-78: Consider adding accessibility attributes to the search input.

The search input would benefit from an aria-label attribute to improve screen reader accessibility, especially since the placeholder text may not always be announced.

Apply this diff:

         <Input
           type="text"
           placeholder="Search templates..."
+          aria-label="Search templates"
           value={searchQuery}
           onChange={(e) => setSearchQuery(e.target.value)}
           className="pl-9 shadow-none"
         />
apps/desktop/src/components/settings/billing.tsx (1)

137-144: Complete the logical grouping of classNames per coding guidelines.

The array is now being passed to cn (✓), but the coding guidelines also specify: "Group Tailwind classNames by logical sections when using cn (split array items by grouping)". The previous review suggested splitting classes by layout/spacing, background, border, and custom overrides.

As per coding guidelines.

Consider applying this diff for clearer logical grouping:

       className={cn(
         [
-          "p-6 flex flex-col gap-6 bg-white",
+          // Layout and spacing
+          "p-6 flex flex-col gap-6",
+          // Background
+          "bg-white",
+          plan.id === "ultimate" && "bg-neutral-50",
+          // Border
           !removeBorder && "border border-neutral-200 rounded-lg",
-          plan.id === "ultimate" && "bg-neutral-50",
+          // Custom overrides
           className,
         ],
       )}
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 98581e1 and feca872.

📒 Files selected for processing (4)
  • apps/desktop/src/components/main/body/shared.tsx (5 hunks)
  • apps/desktop/src/components/main/sidebar/profile/index.tsx (10 hunks)
  • apps/desktop/src/components/settings/billing.tsx (1 hunks)
  • apps/desktop/src/components/settings/template/index.tsx (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • apps/desktop/src/components/main/body/shared.tsx
  • apps/desktop/src/components/main/sidebar/profile/index.tsx
🧰 Additional context used
📓 Path-based instructions (1)
apps/desktop/**/*.{tsx,jsx}

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

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

Files:

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

Applied to files:

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

Applied to files:

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

Applied to files:

  • apps/desktop/src/components/settings/billing.tsx
🧬 Code graph analysis (2)
apps/desktop/src/components/settings/template/index.tsx (2)
packages/db/src/schema.ts (1)
  • templates (101-106)
apps/desktop/src/components/settings/template/editor.tsx (1)
  • TemplateEditor (20-209)
apps/desktop/src/components/settings/billing.tsx (1)
packages/utils/src/cn.ts (1)
  • cn (20-22)
🔇 Additional comments (3)
apps/desktop/src/components/settings/template/index.tsx (2)

22-38: Filtering logic correctly implemented.

The search filter works correctly, and the previous issues have been resolved:

  • The no-op self-assignment has been removed
  • filterStatus is correctly excluded from the dependency array since favorite filtering isn't implemented yet

119-123: LGTM: cn usage follows coding guidelines.

The use of cn with an array and logical grouping of Tailwind classes follows the project's coding guidelines correctly.

Based on coding guidelines.

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

178-230: LGTM! Clean conditional logic for plan actions.

The button logic correctly handles all plan transitions:

  • Shows disabled "Current Plan" when on the selected plan
  • Shows appropriate upgrade/downgrade CTAs with correct variants
  • Delegates to contact sales for Ultimate plan

The implementation properly uses variant="outline" for downgrades and the default primary variant for upgrades, providing good visual hierarchy.

Note: The handlePlanChange handler currently only logs to console and updates local state. Actual billing API integration appears to be deferred, which is appropriate for iterative development.

@yujonglee yujonglee merged commit 27ee254 into main Oct 23, 2025
6 of 7 checks passed
@yujonglee yujonglee deleted the jj-branch-26 branch October 23, 2025 02:38
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 6

Caution

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

⚠️ Outside diff range comments (3)
apps/desktop/src/components/main/sidebar/profile/index.tsx (3)

270-277: Pass an array to cn and group classNames per coding guidelines.

As per coding guidelines.

Apply this diff:

-      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",
-      )}
+      className={cn([
+        // Layout
+        "flex w-full items-center gap-2.5",
+        "px-4 py-2",
+        "text-left",
+        // Interactions
+        "transition-all duration-300",
+        "hover:bg-neutral-100",
+        isExpanded && "bg-neutral-50 border-t border-neutral-100",
+      ])}

281-288: Pass an array to cn and group classNames per coding guidelines.

As per coding guidelines.

Apply this diff:

-        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",
-        )}
+        className={cn([
+          // Layout & sizing
+          "flex size-8 flex-shrink-0 items-center justify-center",
+          "overflow-hidden rounded-full",
+          // Styling
+          "border border-white/60 border-t border-neutral-400",
+          "bg-gradient-to-br from-indigo-400 to-purple-500",
+          "shadow-sm",
+          // Interactions
+          "transition-transform duration-300",
+        ])}

322-327: Pass an array to cn per coding guidelines.

As per coding guidelines.

Apply this diff:

         <ChevronUpIcon
-          className={cn(
-            "h-4 w-4",
-            "transition-transform duration-300",
-            isExpanded ? "rotate-180 text-neutral-500" : "text-neutral-400",
-          )}
+          className={cn([
+            // Sizing
+            "h-4 w-4",
+            // Interactions
+            "transition-transform duration-300",
+            isExpanded ? "rotate-180 text-neutral-500" : "text-neutral-400",
+          ])}
         />
♻️ Duplicate comments (4)
apps/desktop/src/components/settings/template/index.tsx (2)

14-14: Non-functional favorite filter state still present.

The FilterStatus type and filterStatus state remain defined but are not used in the filtering logic. This issue was previously flagged in past reviews.

Also applies to: 19-19


49-67: Non-functional favorite filter buttons still present.

The button group for filtering by "All" or "Favorite" remains in the UI but has no effect on the displayed templates. This creates a confusing user experience and was previously flagged for removal.

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

305-317: Improve className grouping for better readability.

While the code now uses an array for cn() (good!), the classNames could be better organized by logical sections as suggested in the previous review.

Consider applying this grouping:

           className={cn(
             [
+              // Visibility & layout
               "hidden md:inline-block",
-              "cursor-pointer",
               "rounded-full",
-              "border border-neutral-900",
-              "bg-white",
               "px-2.5 py-0.5",
+              // Styling
+              "border border-neutral-900",
+              "bg-white",
               "text-[11px] font-medium text-neutral-900",
-              "transition-colors duration-200",
+              // Interactions
+              "cursor-pointer",
               "hover:bg-neutral-100",
+              "transition-colors duration-200",
             ],
           )}

104-111: Remove console.log and move closeMenu() into promise chain.

Two issues remain:

  1. The console.log should be removed.
  2. As flagged in the previous review, closeMenu() executes synchronously while windowEmitNavigate is still pending. Move closeMenu() into the .then() callback:

Apply this diff:

 const handleClickBilling = useCallback(() => {
-  console.log("handleClickBilling");
   windowsCommands.windowShow({ type: "settings" }).then(() => {
     windowsCommands.windowEmitNavigate({ type: "settings" }, { path: "/app/settings", search: { tab: "billing" } });
+    closeMenu();
   });
-  closeMenu();
 }, [closeMenu]);
🧹 Nitpick comments (3)
apps/desktop/src/components/settings/template/index.tsx (1)

133-153: Consider extracting hook to separate file.

The useTemplates hook could be extracted to apps/desktop/src/hooks/useTemplates.tsx to follow the project's convention of keeping hooks in the hooks directory and improve reusability.

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

254-276: Re-measurement may miss non-window layout changes.

The useEffect only re-measures on window resize events. Layout changes triggered by other factors (e.g., sidebar toggle via setSidebarOpen, flex container reflows) won't trigger re-measurement, potentially causing incorrect maxVisibleItems calculations.

Consider using a ResizeObserver on the cell element to detect any size changes, not just window resizes:

 useEffect(() => {
+  if (!cellRef.current) return;
+
+  const resizeObserver = new ResizeObserver(() => {
     const measureHeight = () => {
       if (cellRef.current && contentRef.current) {
         const cellHeight = cellRef.current.clientHeight;
         const contentTop = contentRef.current.offsetTop;
         const availableHeight = cellHeight - contentTop;
         const EVENT_HEIGHT = 20;
         const SPACING = 4;
         const itemsWithSpacing = Math.floor((availableHeight + SPACING) / (EVENT_HEIGHT + SPACING));
         setMaxVisibleItems(Math.max(1, itemsWithSpacing));
       }
     };
+    measureHeight();
+  });

-  measureHeight();
-  window.addEventListener("resize", measureHeight);
-  return () => window.removeEventListener("resize", measureHeight);
+  resizeObserver.observe(cellRef.current);
+  return () => resizeObserver.disconnect();
 }, []);

226-228: Potential layout shift on initial render.

The initial maxVisibleItems state of 5 may not match the actual available space, causing a brief layout shift when the useEffect runs and updates the value after mount.

To minimize layout shift, you could:

  1. Choose a more conservative initial value (e.g., 3) that's more likely to fit in most layouts
  2. Or, render with visibility: hidden until the first measurement completes

This is a minor UX concern and may not be noticeable in practice.

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between feca872 and 4b85203.

📒 Files selected for processing (10)
  • apps/desktop/src/components/main/body/calendars.tsx (8 hunks)
  • apps/desktop/src/components/main/body/contacts/index.tsx (2 hunks)
  • apps/desktop/src/components/main/body/events.tsx (2 hunks)
  • apps/desktop/src/components/main/body/folders.tsx (4 hunks)
  • apps/desktop/src/components/main/body/humans.tsx (2 hunks)
  • apps/desktop/src/components/main/body/sessions/index.tsx (2 hunks)
  • apps/desktop/src/components/main/body/shared.tsx (5 hunks)
  • apps/desktop/src/components/main/sidebar/profile/index.tsx (10 hunks)
  • apps/desktop/src/components/settings/billing.tsx (1 hunks)
  • apps/desktop/src/components/settings/template/index.tsx (6 hunks)
🚧 Files skipped from review as they are similar to previous changes (5)
  • apps/desktop/src/components/main/body/contacts/index.tsx
  • apps/desktop/src/components/main/body/sessions/index.tsx
  • apps/desktop/src/components/main/body/humans.tsx
  • apps/desktop/src/components/main/body/events.tsx
  • apps/desktop/src/components/settings/billing.tsx
🧰 Additional context used
📓 Path-based instructions (1)
apps/desktop/**/*.{tsx,jsx}

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

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

Files:

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

Applied to files:

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

Applied to files:

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

Applied to files:

  • apps/desktop/src/components/main/body/shared.tsx
  • apps/desktop/src/components/main/sidebar/profile/index.tsx
🧬 Code graph analysis (3)
apps/desktop/src/components/main/body/shared.tsx (2)
apps/desktop/src/hooks/useCmdKeyPressed.tsx (1)
  • useCmdKeyPressed (7-40)
packages/utils/src/cn.ts (1)
  • cn (20-22)
apps/desktop/src/components/settings/template/index.tsx (2)
packages/db/src/schema.ts (1)
  • templates (101-106)
apps/desktop/src/components/settings/template/editor.tsx (1)
  • TemplateEditor (6-47)
apps/desktop/src/components/main/sidebar/profile/index.tsx (2)
apps/desktop/src/components/main/sidebar/profile/banner.tsx (1)
  • TryProBanner (7-70)
packages/utils/src/cn.ts (1)
  • cn (20-22)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: ci (macos, macos-14)
🔇 Additional comments (11)
apps/desktop/src/components/settings/template/index.tsx (4)

22-38: Filtering logic correctly implemented.

The search filtering is working correctly, and previous issues (no-op self-assignment, filterStatus in dependencies) have been properly addressed.


40-42: Clean internal state navigation.

The conditional rendering of TemplateEditor based on internal state is well-implemented and avoids external navigation dependencies.


69-104: Search and template list well-implemented.

The search functionality, empty state UI, and template list rendering are correctly implemented. The onClick handler properly integrates with the internal state navigation pattern.


107-131: TemplateCard follows coding guidelines.

The component correctly uses the cn utility with an array and logical grouping of Tailwind classes, as per the coding guidelines.

As per coding guidelines

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

203-232: LGTM! Exit animation issue resolved.

The banner animation issue from the previous review has been correctly addressed. TryProBanner now stays mounted when not authenticated and receives the isDismissed prop, allowing the exit animation to play properly before the Sign in button appears.

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

25-25: LGTM! Consistent tabIndex prop threading.

The tabIndex prop is properly destructured and forwarded to TabItemBase in both folder variants, aligning with the broader keyboard navigation enhancement across all tab types.

Also applies to: 37-37, 48-48, 63-63

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

3-4: LGTM! Proper cn utility usage per coding guidelines.

The code now correctly uses cn from @hypr/utils instead of clsx, passes arrays, and groups Tailwind classes logically. This addresses the previous review feedback.

As per coding guidelines.

Also applies to: 66-71, 82-87


8-8: LGTM! Clean keyboard shortcut implementation.

The conditional shortcut badge rendering is well-implemented:

  • Uses the useCmdKeyPressed hook to detect Cmd key state
  • Shows + tabIndex badge only when Cmd is pressed and tabIndex is defined
  • Positioned at top-right with pointer-events-none to avoid interfering with interactions

Also applies to: 40-40, 58-58, 93-100


12-12: LGTM! Proper type extension for keyboard navigation.

The tabIndex?: number prop is correctly added to both TabItemProps and TabItemBaseProps, maintaining type safety across the tab component hierarchy.

Also applies to: 19-19, 33-33

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

29-29: LGTM! Consistent tabIndex prop threading.

The tabIndex prop is properly destructured and forwarded to TabItemBase, consistent with other tab components.

Also applies to: 41-41


143-143: LGTM! Button size adjustment.

The "Today" button now uses size="sm" for a more compact appearance, consistent with UI refinements.

Comment on lines +254 to +276
// Measure actual available height and calculate max visible items
useEffect(() => {
const measureHeight = () => {
if (cellRef.current && contentRef.current) {
const cellHeight = cellRef.current.clientHeight;
const contentTop = contentRef.current.offsetTop;
const availableHeight = cellHeight - contentTop;
const EVENT_HEIGHT = 20; // height of each event item (h-5)
const SPACING = 4; // space-y-1

// Calculate how many items can fit
const itemsWithSpacing = Math.floor((availableHeight + SPACING) / (EVENT_HEIGHT + SPACING));
// Reserve space for "+x more" if needed
setMaxVisibleItems(Math.max(1, itemsWithSpacing));
}
};

measureHeight();

// Re-measure on window resize
window.addEventListener("resize", measureHeight);
return () => window.removeEventListener("resize", measureHeight);
}, []);
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Magic numbers hardcoded in height calculation.

The EVENT_HEIGHT (20) and SPACING (4) constants are hardcoded within the useEffect and tightly coupled to CSS classes (h-5, space-y-1). If the CSS changes, this calculation will silently break.

Consider extracting these as constants at the component or module level with clear documentation linking them to the corresponding CSS classes:

+// Must match h-5 class (1.25rem = 20px at default scale)
+const EVENT_HEIGHT = 20;
+// Must match space-y-1 class (0.25rem = 4px at default scale)
+const EVENT_SPACING = 4;
+
 function TabContentCalendarDay({
   day,
   isCurrentMonth,

Then reference these constants in the useEffect. Even better: compute these values from the actual rendered DOM elements instead of hardcoding.

Committable suggestion skipped: line range outside the PR's diff.

Comment on lines +26 to +28
// Mock auth state - toggle this to test different states
const [isAuthenticated, setIsAuthenticated] = useState(false);
const [isBannerDismissed, setIsBannerDismissed] = useState(false);
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Replace mock authentication with real implementation.

The hardcoded mock authentication state should be replaced with actual authentication logic before merging to production.

🤖 Prompt for AI Agents
In apps/desktop/src/components/main/sidebar/profile/index.tsx around lines
26-28, replace the hardcoded mock auth state with the real authentication
source: remove useState(false) for isAuthenticated and instead consume the app's
auth provider/hook (e.g., useAuth() or AuthContext) to derive current
user/authenticated boolean; wire setIsBannerDismissed to persistent/local state
(or context) if required; ensure you subscribe to auth changes (or use the
hook's reactive value) and remove any leftover mock toggles, and add any
necessary cleanup or null checks for user before rendering profile UI.

Comment on lines +38 to +50
const handleAuth = useCallback(() => {
if (isAuthenticated) {
// Mock logout
console.log("Logging out...");
setIsAuthenticated(false);
setIsBannerDismissed(false);
} else {
// Mock sign in
console.log("Signing in...");
setIsAuthenticated(true);
}
closeMenu();
}, [isAuthenticated, closeMenu]);
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Remove debug logs and implement real authentication.

The console.log statements and mock authentication logic need to be replaced with actual sign-in/sign-out implementation.

🤖 Prompt for AI Agents
In apps/desktop/src/components/main/sidebar/profile/index.tsx around lines 38 to
50, remove the console.log debug lines and the mock sign-in/out toggles and
replace them with real authentication calls: call your auth service or hook
(e.g., auth.signIn()/auth.signOut() or useAuth().signIn/signOut), await the call
inside a try/catch, update local state (setIsAuthenticated and
setIsBannerDismissed) based on the real auth response, handle and log errors via
the app logger (not console.log), keep closeMenu() after successful auth, and
add the auth functions to the useCallback dependency list.

Comment on lines +142 to +152
const handleClickProfile = useCallback(() => {
// TODO: Show the user's own profile in the contacts view
openNew({
type: "contacts",
state: {
selectedOrganization: null,
selectedPerson: null,
},
});
closeMenu();
}, [openNew, closeMenu]);
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Complete the My Profile implementation.

The TODO indicates this should show the user's own profile, but currently it opens an empty contacts view identical to the Contacts menu item.

Do you want me to help design a proper implementation that displays the authenticated user's profile, or open an issue to track this task?

🤖 Prompt for AI Agents
In apps/desktop/src/components/main/sidebar/profile/index.tsx around lines
142–152, handleClickProfile currently opens the contacts view with null
selections; instead fetch the authenticated user's identity (via the existing
auth/context selector or a useCurrentUser hook), then call openNew with type
"contacts" and state.selectedPerson set to the current user's person ID (and
state.selectedOrganization to the user's org ID if available). Add the user
dependency to the useCallback dependency array and handle the no-user case by
either opening the generic contacts view or redirecting to login/account
settings.


const USER_TEMPLATE_QUERY = "user_templates";

const quries = persisted.UI.useCreateQueries(
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Fix typo in variable name.

The variable is named quries but should be queries. This typo appears on lines 139 and 151.

Apply this diff to fix the typo:

-  const quries = persisted.UI.useCreateQueries(
+  const queries = persisted.UI.useCreateQueries(
     store,
     (store) =>
       createQueries(store).setQueryDefinition(USER_TEMPLATE_QUERY, "templates", ({ select, where }) => {
         select("title");
         select("description");
         select("sections");
         where("user_id", user_id ?? "");
       }),
     [user_id],
   );

-  const templates = persisted.UI.useResultTable(USER_TEMPLATE_QUERY, quries);
+  const templates = persisted.UI.useResultTable(USER_TEMPLATE_QUERY, queries);

Also applies to: 151-151

🤖 Prompt for AI Agents
In apps/desktop/src/components/settings/template/index.tsx around lines 139 and
151, the local variable is misspelled as `quries`; rename it to `queries` in
both occurrences and update any references in this file to use the corrected
identifier so imports/uses remain consistent and TypeScript/ESLint errors are
resolved.

);

const templates = persisted.UI.useResultTable(USER_TEMPLATE_QUERY, quries);
return templates as unknown as Record<string, persisted.Template>;
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Avoid double type casting; fix type mismatch.

The as unknown as pattern bypasses TypeScript's type safety and indicates a type mismatch between useResultTable's return type and persisted.Template. This could lead to runtime errors if the types don't actually match.

Either fix the underlying type definitions or add runtime validation to ensure type safety.

🤖 Prompt for AI Agents
In apps/desktop/src/components/settings/template/index.tsx around line 152,
remove the unsafe "as unknown as Record<string, persisted.Template>" cast and
fix the underlying mismatch: either update the useResultTable return type to
accurately reflect Record<string, persisted.Template>, or map/transform the
returned templates into a new object that conforms to persisted.Template
(validating required fields and converting types where necessary) and then
return that safely typed object; if runtime shapes may vary, add explicit
runtime checks (throw or log and skip invalid entries) before returning to
preserve type safety.

@coderabbitai coderabbitai bot mentioned this pull request Dec 13, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants