Skip to content

Conversation

@AnthonyRonning
Copy link
Contributor

@AnthonyRonning AnthonyRonning commented Dec 29, 2025

Summary

Add the ability to select and delete multiple chats at once.

Changes

  • Add 'Select' option to chat dropdown menu (...) to enter selection mode
  • Support selecting up to 20 chats for bulk deletion
  • Use batch delete API from @opensecret/react 1.5.3
  • Long-press on mobile enters selection mode
  • Selection mode shows selected count and Delete button in sidebar header
  • New BulkDeleteDialog component for confirming bulk deletions

How to use

  1. Click the '...' menu on any chat
  2. Click 'Select' to enter selection mode
  3. Tap/click additional chats to select (up to 20)
  4. Click 'Delete' button to bulk delete
  5. Confirm in the dialog

On mobile, you can also long-press a chat to enter selection mode.

Closes #351

Summary by CodeRabbit

  • New Features

    • Bulk deletion flow with confirmation dialog to permanently delete multiple chats.
    • Selection mode with visual checkboxes, per-item selection, header controls showing selected count, and cancel/delete actions.
    • Long-press on mobile to enter selection mode and streamlined item actions while selecting.
  • Chores

    • Updated a frontend dependency version.

✏️ Tip: You can customize this high-level summary in your review settings.

@cloudflare-workers-and-pages
Copy link

cloudflare-workers-and-pages bot commented Dec 29, 2025

Deploying maple with  Cloudflare Pages  Cloudflare Pages

Latest commit: 3fadde1
Status: ✅  Deploy successful!
Preview URL: https://04995de0.maple-ca8.pages.dev
Branch Preview URL: https://feature-multi-chat-delete.maple-ca8.pages.dev

View logs

@greptile-apps
Copy link
Contributor

greptile-apps bot commented Dec 29, 2025

Greptile Summary

This PR adds multi-chat deletion functionality with a polished UX that matches modern mobile and desktop patterns. Users can enter selection mode by clicking "Select" in the dropdown menu or long-pressing on mobile, select up to 20 chats with visual checkboxes, and batch delete with confirmation.

Key changes:

  • Added BulkDeleteDialog component for confirming bulk deletions
  • Integrated batch delete API from @opensecret/react 1.5.3
  • Long-press gesture (500ms) on mobile enters selection mode automatically
  • Selection mode replaces the History header with selection count and Delete button
  • Handles both API conversations (batch delete) and archived chats (individual deletes)
  • Properly updates UI state and navigates away if current chat is deleted
  • Uses custom event openbulkdelete to coordinate dialog opening between components

The implementation is clean and follows existing patterns in the codebase. Selection mode UX could be slightly snappier by immediately entering mode when clicking "Select" rather than relying on useEffect detection.

Confidence Score: 4/5

  • This PR is safe to merge with low risk
  • Well-structured feature with proper error handling and state management. The batch delete API integration looks solid, and the UI gracefully handles edge cases (current chat deletion, max selection limit). Minor UX optimization opportunity exists for selection mode entry, but no functional issues identified.
  • No files require special attention

Important Files Changed

Filename Overview
frontend/src/components/BulkDeleteDialog.tsx New dialog component for bulk delete confirmation with proper loading states
frontend/src/components/ChatHistoryList.tsx Adds multi-select mode with long-press support, batch delete logic, and proper error handling; handles archived chats individually
frontend/src/components/Sidebar.tsx Manages selection mode state and displays selection UI with count and delete button in header

Sequence Diagram

sequenceDiagram
    participant User
    participant Sidebar
    participant ChatHistoryList
    participant BulkDeleteDialog
    participant OpenSecret API
    participant LocalState

    User->>ChatHistoryList: Click "Select" in dropdown
    ChatHistoryList->>Sidebar: onSelectionChange(Set([chatId]))
    Sidebar->>Sidebar: useEffect detects selectedIds.size > 0
    Sidebar->>Sidebar: setIsSelectionMode(true)
    Sidebar->>ChatHistoryList: Pass isSelectionMode=true
    ChatHistoryList->>User: Show checkboxes & selection UI

    User->>ChatHistoryList: Click additional chats (up to 20)
    ChatHistoryList->>ChatHistoryList: toggleSelection(chatId)
    ChatHistoryList->>Sidebar: onSelectionChange(new Set)

    User->>Sidebar: Click "Delete" button
    Sidebar->>Window: dispatchEvent("openbulkdelete")
    Window->>ChatHistoryList: Event listener triggered
    ChatHistoryList->>BulkDeleteDialog: setIsBulkDeleteDialogOpen(true)
    BulkDeleteDialog->>User: Show confirmation dialog

    User->>BulkDeleteDialog: Confirm deletion
    BulkDeleteDialog->>ChatHistoryList: onConfirm()
    ChatHistoryList->>ChatHistoryList: handleBulkDelete()

    alt API Conversations
        ChatHistoryList->>OpenSecret API: batchDeleteConversations(ids)
        OpenSecret API-->>ChatHistoryList: {data: [{id, deleted}]}
        ChatHistoryList->>ChatHistoryList: Remove deleted from state
    end

    alt Archived Chats
        loop For each archived chat
            ChatHistoryList->>LocalState: deleteChat(id)
            LocalState-->>ChatHistoryList: Success
        end
        ChatHistoryList->>ChatHistoryList: invalidateQueries("archivedChats")
    end

    alt Current chat deleted
        ChatHistoryList->>Window: Update URL & dispatch "newchat"
    end

    ChatHistoryList->>Sidebar: onExitSelectionMode()
    Sidebar->>Sidebar: Clear selection state
    Sidebar->>ChatHistoryList: isSelectionMode=false
Loading

@coderabbitai
Copy link

coderabbitai bot commented Dec 29, 2025

📝 Walkthrough

Walkthrough

Adds multi-chat selection and bulk-delete UI and logic: a new BulkDeleteDialog component, selection mode with checkboxes and mobile long-press in ChatHistoryList, and Sidebar controls to manage selections and trigger bulk deletion. Also bumps @opensecret/react to 1.5.3.

Changes

Cohort / File(s) Summary
Dependency Update
frontend/package.json
Bumped @opensecret/react from 1.5.2 to 1.5.3.
New Component
frontend/src/components/BulkDeleteDialog.tsx
Added BulkDeleteDialog confirmation AlertDialog with pluralized copy, loading state, disabled actions while deleting, and onConfirm callback.
Selection Mode & Bulk Delete
frontend/src/components/ChatHistoryList.tsx
Added selection-mode props (isSelectionMode, onExitSelectionMode, selectedIds, onSelectionChange); per-item checkboxes and selected state; mobile long-press entry; bulk-delete dialog wiring and batching deletion logic; suppressed per-item dropdown in selection mode.
Selection State & Controls
frontend/src/components/Sidebar.tsx
Added multi-select state (isSelectionMode, selectedIds), selection control bar (cancel + selected count + delete), Trash2/X icon imports, and prop passthroughs to ChatHistoryList to coordinate selection and bulk delete.

Sequence Diagram

sequenceDiagram
    participant User
    participant Sidebar
    participant ChatHistoryList
    participant BulkDeleteDialog
    participant API

    rect rgb(240,248,255)
    Note over User,ChatHistoryList: Enter selection
    User->>ChatHistoryList: Long-press item / click to select
    ChatHistoryList->>ChatHistoryList: Toggle selection, update selectedIds
    ChatHistoryList->>Sidebar: onSelectionChange(selectedIds)
    Sidebar->>Sidebar: Enter selection mode / render control bar
    end

    rect rgb(255,250,240)
    User->>Sidebar: Click Delete
    Sidebar->>BulkDeleteDialog: Open (count = selectedIds.size)
    User->>BulkDeleteDialog: Confirm
    BulkDeleteDialog->>Sidebar: onConfirm
    Sidebar->>API: Delete selected conversations (batch) / archived items individually
    API-->>Sidebar: Deletion result
    Sidebar->>ChatHistoryList: onExitSelectionMode()
    ChatHistoryList->>ChatHistoryList: Clear selections, exit mode
    ChatHistoryList->>Sidebar: onSelectionChange(empty set)
    Sidebar->>Sidebar: Exit selection mode / refresh UI
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Poem

🐰 I long-pressed a thread and gave a tiny hop,

boxes appeared — a carrot crop!
A dialog asked softly, "Shall we proceed?"
I tapped Delete; gone went the feed.
Hopping off, tidy and quick — that’s my dev-hop trick. 🥕✨

Pre-merge checks and finishing touches

✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main change: adding multi-chat delete functionality, which is the primary objective of this PR.
Linked Issues check ✅ Passed The PR fully implements the requirement from issue #351 to enable users to select and delete multiple chats simultaneously through multiple interaction patterns.
Out of Scope Changes check ✅ Passed The dependency update to @opensecret/react v1.5.3 directly supports the bulk delete functionality requirement, and all code changes are directly related to implementing multi-chat selection and deletion features.
✨ Finishing touches
  • 📝 Generate docstrings

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

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (3)
frontend/src/components/ChatHistoryList.tsx (3)

26-34: selectedIds and onSelectionChange should either be optional or have defaults.

The props selectedIds and onSelectionChange are required but isSelectionMode defaults to false. If the component is used without selection support, this could cause runtime issues.

🔎 Suggested fix: Make selection props optional with defaults
 interface ChatHistoryListProps {
   currentChatId?: string;
   searchQuery?: string;
   isMobile?: boolean;
   isSelectionMode?: boolean;
   onExitSelectionMode?: () => void;
-  selectedIds: Set<string>;
-  onSelectionChange: (ids: Set<string>) => void;
+  selectedIds?: Set<string>;
+  onSelectionChange?: (ids: Set<string>) => void;
 }

Then update the destructuring to provide defaults:

   isSelectionMode = false,
   onExitSelectionMode,
-  selectedIds,
-  onSelectionChange
+  selectedIds = new Set(),
+  onSelectionChange = () => {}
 }: ChatHistoryListProps) {

344-362: Consider providing user feedback when selection limit is reached.

The toggleSelection silently returns when MAX_SELECTION is hit. Users may not understand why additional selections aren't working.

A toast notification or visual indicator when the 20-chat limit is reached would improve UX.


364-427: Bulk delete logic handles partial failures well, but users won't see error feedback.

The implementation correctly:

  • Separates archived vs API conversations
  • Only removes successfully deleted items from state (Line 383-385)
  • Cleans up selection state after completion

However, if batchDeleteConversations fails or partially fails, errors are only logged to console. Consider surfacing errors to users via toast notification.

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between cfddba9 and 38ed7b4.

⛔ Files ignored due to path filters (1)
  • frontend/bun.lock is excluded by !**/*.lock
📒 Files selected for processing (4)
  • frontend/package.json
  • frontend/src/components/BulkDeleteDialog.tsx
  • frontend/src/components/ChatHistoryList.tsx
  • frontend/src/components/Sidebar.tsx
🧰 Additional context used
📓 Path-based instructions (1)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.{ts,tsx}: Use path aliases (@/* maps to ./src/*) for imports in TypeScript/React files
Use 2-space indentation, double quotes, and enforce 100-character line limit in TypeScript/React code
Maintain strict TypeScript and avoid using any type
Use PascalCase for component names and camelCase for variables and function names
Use functional components with React hooks instead of class components
Use React context for global state management and TanStack Query for server state management
Run just format, just lint, and just build after making TypeScript/React changes to ensure code quality and compilation

Files:

  • frontend/src/components/BulkDeleteDialog.tsx
  • frontend/src/components/Sidebar.tsx
  • frontend/src/components/ChatHistoryList.tsx
🧠 Learnings (3)
📚 Learning: 2025-12-08T19:55:33.330Z
Learnt from: CR
Repo: OpenSecretCloud/Maple PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-08T19:55:33.330Z
Learning: Applies to **/*.{ts,tsx} : Use React context for global state management and TanStack Query for server state management

Applied to files:

  • frontend/src/components/Sidebar.tsx
  • frontend/src/components/ChatHistoryList.tsx
📚 Learning: 2025-12-08T19:55:33.330Z
Learnt from: CR
Repo: OpenSecretCloud/Maple PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-08T19:55:33.330Z
Learning: Applies to src/components/ui/**/*.{ts,tsx} : Use existing shadcn/ui components from `src/components/ui/` for UI elements instead of creating custom components

Applied to files:

  • frontend/src/components/Sidebar.tsx
📚 Learning: 2025-12-08T19:55:33.330Z
Learnt from: CR
Repo: OpenSecretCloud/Maple PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-08T19:55:33.330Z
Learning: Applies to **/*.{ts,tsx} : Use functional components with React hooks instead of class components

Applied to files:

  • frontend/src/components/Sidebar.tsx
  • frontend/src/components/ChatHistoryList.tsx
🧬 Code graph analysis (2)
frontend/src/components/BulkDeleteDialog.tsx (1)
frontend/src/components/ui/alert-dialog.tsx (8)
  • AlertDialog (104-104)
  • AlertDialogContent (108-108)
  • AlertDialogHeader (109-109)
  • AlertDialogTitle (111-111)
  • AlertDialogDescription (112-112)
  • AlertDialogFooter (110-110)
  • AlertDialogCancel (114-114)
  • AlertDialogAction (113-113)
frontend/src/components/ChatHistoryList.tsx (3)
frontend/src/components/ui/checkbox.tsx (1)
  • Checkbox (26-26)
frontend/src/components/ui/dropdown-menu.tsx (4)
  • DropdownMenu (170-170)
  • DropdownMenuTrigger (171-171)
  • DropdownMenuContent (172-172)
  • DropdownMenuItem (173-173)
frontend/src/components/BulkDeleteDialog.tsx (1)
  • BulkDeleteDialog (20-57)
⏰ 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). (4)
  • GitHub Check: build-android
  • GitHub Check: build-ios
  • GitHub Check: build-macos (universal-apple-darwin)
  • GitHub Check: Cloudflare Pages
🔇 Additional comments (11)
frontend/src/components/BulkDeleteDialog.tsx (1)

20-57: Well-structured confirmation dialog.

Clean implementation with proper handling:

  • Pluralization logic for count-based messaging
  • isDeleting state disables buttons to prevent double-submit
  • e.preventDefault() ensures the dialog stays open during async deletion
  • Destructive styling clearly indicates the action severity
frontend/src/components/Sidebar.tsx (4)

33-47: Selection state management looks good.

The auto-activation of selection mode when items are selected (e.g., via long-press from ChatHistoryList) provides smooth UX. The exitSelectionMode callback properly clears both states.


49-53: Event-based approach for triggering bulk delete dialog is reasonable.

This decouples the Sidebar from the dialog implementation in ChatHistoryList. The guard condition prevents unnecessary events.


190-230: Selection mode header UI is well-implemented.

Clear visual distinction between modes with appropriate controls. The cancel button, selection count, and destructive delete button provide good UX.


255-263: Props correctly wire selection state between Sidebar and ChatHistoryList.

The lifted state pattern allows coordinated selection management across components.

frontend/src/components/ChatHistoryList.tsx (5)

429-447: Long-press handlers are correctly implemented.

The 500ms threshold is appropriate, and the timer cleanup prevents memory leaks and ghost activations.


449-458: Event listener properly coordinates with Sidebar's delete trigger.

Cleanup is correctly handled in the effect's return function.


590-617: Selection UI integration is well-executed.

Good implementation choices:

  • Click behavior adapts based on isSelectionMode
  • Checkbox stops propagation to prevent double-toggle
  • Long-press handlers are guarded by isMobile check
  • Visual feedback with bg-primary/10 for selected items

676-744: Archived chats section doesn't support bulk selection.

This may be intentional, but users might expect consistent behavior. Consider adding selection support to archived chats in a follow-up, or document this limitation.


764-770: BulkDeleteDialog correctly integrated.

All props are properly connected to component state and handlers.

frontend/package.json (1)

20-20: LGTM – v1.5.3 exports batchDeleteConversations and is correctly used in ChatHistoryList.tsx.

@AnthonyRonning
Copy link
Contributor Author

@TestFlight build

@github-actions
Copy link
Contributor

🚀 TestFlight deployment triggered! Check the Actions tab for progress.

@github-actions
Copy link
Contributor

✅ TestFlight deployment completed successfully!

@AnthonyRonning AnthonyRonning force-pushed the feature/multi-chat-delete branch from 38ed7b4 to 61bfbeb Compare December 29, 2025 21:58
@AnthonyRonning
Copy link
Contributor Author

@TestFlight build

@github-actions
Copy link
Contributor

🚀 TestFlight deployment triggered! Check the Actions tab for progress.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

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

⚠️ Outside diff range comments (1)
frontend/src/components/ChatHistoryList.tsx (1)

677-745: Archived chats excluded from selection mode – handleBulkDelete contains unreachable code.

The archived chats section (lines 677-745) does not render selection checkboxes or integrate with selection mode, making it impossible for archived chat IDs to end up in selectedIds. Consequently, the archived chat handling logic in handleBulkDelete (lines 373-398) is unreachable dead code that will never execute.

This appears intentional—archived chats are managed separately via the dropdown menu (Rename Chat, Delete Chat options). Either add a code comment in handleBulkDelete explaining the archived chat filtering is defensive/historical, or remove the unreachable archived chat branch to clarify intent.

🧹 Nitpick comments (3)
frontend/src/components/Sidebar.tsx (1)

190-231: Selection mode UI is well-designed.

Good UX considerations:

  • Clear visual distinction between modes
  • "max" indicator when limit (20) is reached
  • Delete button disabled when no items selected
  • Proper aria-label for accessibility on the cancel button

One minor note: the magic number 20 on Line 204 should ideally reference the same constant (MAX_SELECTION) defined in ChatHistoryList.tsx to avoid duplication. Consider extracting it to a shared location.

frontend/src/components/ChatHistoryList.tsx (2)

344-362: Selection toggle logic is correct.

The implementation properly creates a new Set to avoid mutation and correctly enforces the MAX_SELECTION limit.

Consider adding user feedback (e.g., a toast notification) when the selection limit is reached, as the silent return may leave users confused about why additional items can't be selected.


365-427: Bulk delete implementation handles the happy path well.

Good practices:

  • Separates archived chats from API conversations for different handling
  • Uses batch API (batchDeleteConversations) for efficiency
  • Correctly filters only successfully deleted items from local state using result.data
  • Properly handles navigation when current chat is deleted

A few considerations:

  1. Error feedback: On Line 414, errors are logged but no user feedback is provided. Consider adding a toast notification for failures.

  2. Partial failure resilience: The sequential loop for archived chats (Lines 390-394) could fail midway. The code continues and clears selection regardless, which may leave the UI in an inconsistent state if some deletions failed.

🔎 Suggested improvement for error handling
     // Delete archived chats individually
+    const failedArchivedIds: string[] = [];
     for (const id of archivedIds) {
       if (localState?.deleteChat) {
-        await localState.deleteChat(id);
+        try {
+          await localState.deleteChat(id);
+        } catch (err) {
+          console.error(`Failed to delete archived chat ${id}:`, err);
+          failedArchivedIds.push(id);
+        }
       }
     }
+
+    // Optionally notify user of partial failures
+    if (failedArchivedIds.length > 0) {
+      // Consider showing a toast: `${failedArchivedIds.length} chat(s) failed to delete`
+    }
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 38ed7b4 and 61bfbeb.

⛔ Files ignored due to path filters (1)
  • frontend/bun.lock is excluded by !**/*.lock
📒 Files selected for processing (4)
  • frontend/package.json
  • frontend/src/components/BulkDeleteDialog.tsx
  • frontend/src/components/ChatHistoryList.tsx
  • frontend/src/components/Sidebar.tsx
🚧 Files skipped from review as they are similar to previous changes (1)
  • frontend/package.json
🧰 Additional context used
📓 Path-based instructions (1)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.{ts,tsx}: Use path aliases (@/* maps to ./src/*) for imports in TypeScript/React files
Use 2-space indentation, double quotes, and enforce 100-character line limit in TypeScript/React code
Maintain strict TypeScript and avoid using any type
Use PascalCase for component names and camelCase for variables and function names
Use functional components with React hooks instead of class components
Use React context for global state management and TanStack Query for server state management
Run just format, just lint, and just build after making TypeScript/React changes to ensure code quality and compilation

Files:

  • frontend/src/components/BulkDeleteDialog.tsx
  • frontend/src/components/ChatHistoryList.tsx
  • frontend/src/components/Sidebar.tsx
🧠 Learnings (3)
📚 Learning: 2025-12-08T19:55:33.330Z
Learnt from: CR
Repo: OpenSecretCloud/Maple PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-08T19:55:33.330Z
Learning: Applies to **/*.{ts,tsx} : Use React context for global state management and TanStack Query for server state management

Applied to files:

  • frontend/src/components/ChatHistoryList.tsx
  • frontend/src/components/Sidebar.tsx
📚 Learning: 2025-12-08T19:55:33.330Z
Learnt from: CR
Repo: OpenSecretCloud/Maple PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-08T19:55:33.330Z
Learning: Applies to **/*.{ts,tsx} : Use functional components with React hooks instead of class components

Applied to files:

  • frontend/src/components/ChatHistoryList.tsx
  • frontend/src/components/Sidebar.tsx
📚 Learning: 2025-12-08T19:55:33.330Z
Learnt from: CR
Repo: OpenSecretCloud/Maple PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-08T19:55:33.330Z
Learning: Applies to src/components/ui/**/*.{ts,tsx} : Use existing shadcn/ui components from `src/components/ui/` for UI elements instead of creating custom components

Applied to files:

  • frontend/src/components/Sidebar.tsx
🧬 Code graph analysis (3)
frontend/src/components/BulkDeleteDialog.tsx (1)
frontend/src/components/ui/alert-dialog.tsx (8)
  • AlertDialog (104-104)
  • AlertDialogContent (108-108)
  • AlertDialogHeader (109-109)
  • AlertDialogTitle (111-111)
  • AlertDialogDescription (112-112)
  • AlertDialogFooter (110-110)
  • AlertDialogCancel (114-114)
  • AlertDialogAction (113-113)
frontend/src/components/ChatHistoryList.tsx (3)
frontend/src/components/ui/checkbox.tsx (1)
  • Checkbox (26-26)
frontend/src/components/ui/dropdown-menu.tsx (4)
  • DropdownMenu (170-170)
  • DropdownMenuTrigger (171-171)
  • DropdownMenuContent (172-172)
  • DropdownMenuItem (173-173)
frontend/src/components/BulkDeleteDialog.tsx (1)
  • BulkDeleteDialog (20-57)
frontend/src/components/Sidebar.tsx (3)
frontend/src/components/ui/button.tsx (1)
  • Button (62-62)
frontend/src/components/icons/X.tsx (1)
  • X (3-23)
frontend/src/components/ChatHistoryList.tsx (1)
  • ChatHistoryList (53-774)
⏰ 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). (6)
  • GitHub Check: Greptile Review
  • GitHub Check: build-macos (universal-apple-darwin)
  • GitHub Check: build-linux
  • GitHub Check: build-android
  • GitHub Check: build-ios
  • GitHub Check: Cloudflare Pages
🔇 Additional comments (9)
frontend/src/components/BulkDeleteDialog.tsx (1)

1-57: Well-structured bulk delete confirmation dialog.

The component follows React best practices with proper TypeScript typing, uses the path alias correctly (@/components/ui/alert-dialog), and implements good UX patterns:

  • Disables both buttons during deletion to prevent double-clicks
  • Correctly pluralizes "chat(s)" based on count
  • Uses e.preventDefault() to prevent dialog auto-close before async operation completes
frontend/src/components/Sidebar.tsx (4)

1-9: Imports look correct.

The X icon is correctly imported from lucide-react (the close/cancel icon), distinct from the custom X.tsx component in icons/ which is the Twitter/X logo. Good distinction.


37-42: Selection mode auto-activation logic is correct.

This effect enables the long-press flow: when ChatHistoryList sets selectedIds via onSelectionChange, this effect automatically enters selection mode. The dependency array correctly uses selectedIds.size as an optimization since only the count matters for this logic.


49-53: Window event for triggering bulk delete dialog.

Using window.dispatchEvent for cross-component communication works but is less type-safe than alternatives like React context. This pattern is already established in the codebase (e.g., "newchat", "conversationselected" events), so it's consistent with existing patterns.


257-265: Props correctly passed to ChatHistoryList.

The selection-related state is appropriately lifted to Sidebar and passed down, enabling coordinated selection UI in the sidebar header and list items.

frontend/src/components/ChatHistoryList.tsx (4)

26-34: Props interface correctly extended for selection mode.

The use of Set<string> for selectedIds is appropriate for efficient membership checks. Making selectedIds and onSelectionChange required ensures this is a controlled component pattern.


450-458: Event listener setup is correct.

The effect properly adds and removes the event listener, and the dependency on selectedIds.size ensures the handler always has access to the current selection state.


591-618: Selection mode click handling and checkbox UI are well-implemented.

Good implementation details:

  • Dual-mode click handler correctly distinguishes between selection toggle and navigation
  • Long-press handlers are properly attached for mobile
  • Checkbox includes stopPropagation to prevent double-toggle from the parent's onClick
  • onContextMenu is prevented to avoid conflicts with mobile long-press

765-771: BulkDeleteDialog correctly integrated.

Props are properly wired to component state and handlers. The controlled dialog pattern (always rendered, visibility controlled by open prop) is correctly implemented.

Copy link
Contributor

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

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

Additional Comments (1)

  1. frontend/src/components/ChatHistoryList.tsx, line 694-741 (link)

    logic: Archived chats don't support selection mode - users can't include archived chats in bulk deletions. Apply the same selection mode pattern used for regular conversations (checkbox, conditional click handler, Select menu item)

4 files reviewed, 1 comment

Edit Code Review Agent Settings | Greptile

@AnthonyRonning AnthonyRonning force-pushed the feature/multi-chat-delete branch from 61bfbeb to 3fadde1 Compare December 29, 2025 22:05
- Add 'Select' option to chat dropdown menu to enter selection mode
- Support selecting up to 20 chats for bulk deletion
- Use batch delete API from @opensecret/react 1.5.3
- Long-press on mobile enters selection mode
- Selection mode shows count and Delete button in sidebar header

Closes #351

Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
Copy link
Contributor

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

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

4 files reviewed, 1 comment

Edit Code Review Agent Settings | Greptile

Comment on lines +654 to +657
<DropdownMenuItem onClick={() => onSelectionChange(new Set([conv.id]))}>
<CheckSquare className="mr-2 h-4 w-4" />
<span>Select</span>
</DropdownMenuItem>
Copy link
Contributor

Choose a reason for hiding this comment

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

style: The "Select" action doesn't immediately show selection mode UI because Sidebar detects selection mode via useEffect watching selectedIds.size. Consider explicitly triggering selection mode when clicking "Select" for instant visual feedback.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Prompt To Fix With AI
This is a comment left during a code review.
Path: frontend/src/components/ChatHistoryList.tsx
Line: 654:657

Comment:
**style:** The "Select" action doesn't immediately show selection mode UI because `Sidebar` detects selection mode via `useEffect` watching `selectedIds.size`. Consider explicitly triggering selection mode when clicking "Select" for instant visual feedback.

<sub>Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!</sub>

How can I resolve this? If you propose a fix, please make it concise.

@github-actions
Copy link
Contributor

✅ TestFlight deployment completed successfully!

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
frontend/src/components/BulkDeleteDialog.tsx (1)

27-30: Consider removing e.preventDefault() as it may be unnecessary.

The AlertDialogAction component likely handles button click events correctly without requiring preventDefault(). Unless there's a specific reason (e.g., preventing form submission), this line can be removed to simplify the code.

🔎 Simplified implementation
-  const handleConfirm = (e: React.MouseEvent) => {
-    e.preventDefault();
-    onConfirm();
-  };
+  const handleConfirm = () => {
+    onConfirm();
+  };

Or even simpler, pass onConfirm directly:

         <AlertDialogAction
-          onClick={handleConfirm}
+          onClick={onConfirm}
           disabled={isDeleting}
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 61bfbeb and 3fadde1.

⛔ Files ignored due to path filters (1)
  • frontend/bun.lock is excluded by !**/*.lock
📒 Files selected for processing (4)
  • frontend/package.json
  • frontend/src/components/BulkDeleteDialog.tsx
  • frontend/src/components/ChatHistoryList.tsx
  • frontend/src/components/Sidebar.tsx
🚧 Files skipped from review as they are similar to previous changes (1)
  • frontend/package.json
🧰 Additional context used
📓 Path-based instructions (1)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.{ts,tsx}: Use path aliases (@/* maps to ./src/*) for imports in TypeScript/React files
Use 2-space indentation, double quotes, and enforce 100-character line limit in TypeScript/React code
Maintain strict TypeScript and avoid using any type
Use PascalCase for component names and camelCase for variables and function names
Use functional components with React hooks instead of class components
Use React context for global state management and TanStack Query for server state management
Run just format, just lint, and just build after making TypeScript/React changes to ensure code quality and compilation

Files:

  • frontend/src/components/BulkDeleteDialog.tsx
  • frontend/src/components/ChatHistoryList.tsx
  • frontend/src/components/Sidebar.tsx
🧠 Learnings (3)
📚 Learning: 2025-12-08T19:55:33.330Z
Learnt from: CR
Repo: OpenSecretCloud/Maple PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-08T19:55:33.330Z
Learning: Applies to **/*.{ts,tsx} : Use React context for global state management and TanStack Query for server state management

Applied to files:

  • frontend/src/components/ChatHistoryList.tsx
  • frontend/src/components/Sidebar.tsx
📚 Learning: 2025-12-08T19:55:33.330Z
Learnt from: CR
Repo: OpenSecretCloud/Maple PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-08T19:55:33.330Z
Learning: Applies to **/*.{ts,tsx} : Use functional components with React hooks instead of class components

Applied to files:

  • frontend/src/components/ChatHistoryList.tsx
  • frontend/src/components/Sidebar.tsx
📚 Learning: 2025-12-08T19:55:33.330Z
Learnt from: CR
Repo: OpenSecretCloud/Maple PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-08T19:55:33.330Z
Learning: Applies to src/components/ui/**/*.{ts,tsx} : Use existing shadcn/ui components from `src/components/ui/` for UI elements instead of creating custom components

Applied to files:

  • frontend/src/components/Sidebar.tsx
🧬 Code graph analysis (2)
frontend/src/components/BulkDeleteDialog.tsx (1)
frontend/src/components/ui/alert-dialog.tsx (8)
  • AlertDialog (104-104)
  • AlertDialogContent (108-108)
  • AlertDialogHeader (109-109)
  • AlertDialogTitle (111-111)
  • AlertDialogDescription (112-112)
  • AlertDialogFooter (110-110)
  • AlertDialogCancel (114-114)
  • AlertDialogAction (113-113)
frontend/src/components/Sidebar.tsx (2)
frontend/src/components/icons/X.tsx (1)
  • X (3-23)
frontend/src/components/ChatHistoryList.tsx (1)
  • ChatHistoryList (53-784)
⏰ 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). (6)
  • GitHub Check: Greptile Review
  • GitHub Check: build-ios
  • GitHub Check: build-android
  • GitHub Check: build-macos (universal-apple-darwin)
  • GitHub Check: build-linux
  • GitHub Check: Cloudflare Pages
🔇 Additional comments (9)
frontend/src/components/BulkDeleteDialog.tsx (1)

20-57: LGTM! Clean confirmation dialog implementation.

The component follows React best practices with clear prop types, proper pluralization, and good UX through disabled states and loading indicators. The implementation aligns well with the project's patterns using shadcn/ui components.

frontend/src/components/Sidebar.tsx (3)

33-53: LGTM! Selection mode state management is well-structured.

The auto-entry effect (lines 38-42) provides good UX for mobile long-press, and the state cleanup in exitSelectionMode is properly memoized. The custom event dispatch pattern works effectively for component communication.


190-231: LGTM! Selection mode UI is intuitive and well-implemented.

The conditional rendering between selection and normal modes is clear, with good UX touches like showing "max" at 20 selections (line 204) and properly disabling the delete button when no items are selected (line 212).


257-265: LGTM! Selection props correctly passed to ChatHistoryList.

The component properly threads selection state and callbacks to ChatHistoryList, enabling the parent-child coordination for bulk selection and deletion workflows.

frontend/src/components/ChatHistoryList.tsx (5)

344-362: LGTM! Selection toggle logic with proper limit enforcement.

The MAX_SELECTION limit of 20 (line 344) is enforced correctly, and the Set manipulation for toggling selections is clean and efficient.


449-457: LGTM! Long press timer cleanup properly implemented.

This cleanup effect addresses the previous review comment by ensuring the long-press timer is cleared on unmount, preventing the callback from running after the component is unmounted.


430-447: LGTM! Long press handlers well-implemented for mobile UX.

The 500ms threshold (line 437) is appropriate for mobile long-press detection, and the handlers correctly integrate with the cleanup effect to prevent memory leaks.


586-674: LGTM! Conversation item rendering correctly adapts to selection mode.

The conditional rendering cleanly separates selection mode behavior from normal mode:

  • Click behavior toggles selection vs. navigation (lines 602-608)
  • Checkbox and adjusted layout in selection mode (lines 619-628)
  • Dropdown hidden during selection (line 638)
  • "Select" option in dropdown menu provides alternative entry to selection mode (line 654)

The implementation provides good UX on both desktop and mobile.


775-781: LGTM! BulkDeleteDialog properly integrated.

The dialog is correctly wired with appropriate state and handlers, completing the bulk delete flow.

@AnthonyRonning AnthonyRonning merged commit 3c9d5b0 into master Dec 29, 2025
15 checks passed
@AnthonyRonning AnthonyRonning deleted the feature/multi-chat-delete branch December 29, 2025 22:50
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.

Multi-chat Delete

2 participants