Skip to content

Feature/global search#36

Merged
ihabadham merged 13 commits intodevelopfrom
feature/global-search
Jun 6, 2025
Merged

Feature/global search#36
ihabadham merged 13 commits intodevelopfrom
feature/global-search

Conversation

@ihabadham
Copy link
Owner

@ihabadham ihabadham commented Jun 6, 2025

closes #33

Summary by CodeRabbit

  • New Features

    • Introduced a global search bar in the dashboard, enabling users to search across tasks, notes, and habits with real-time results and category filters.
    • Added keyboard navigation and interactive dropdown for search results, allowing quick access to matched items.
    • Search results are organized by type and display relevant metadata for each item.
  • Performance Improvements

    • Implemented backend optimizations with new indexes on tasks, notes, and habits for faster search queries.
  • Bug Fixes

    • Improved navigation logic to handle initial state when accessing notes from dashboard or search.
  • Chores

    • Added required dependencies to support debounced search functionality.

ihabadham added 7 commits June 6, 2025 03:46
- Added search controller with cross-content search functionality
- Created search routes
- Updated app.js to register search routes
- Added text indexes to models for optimized search performance
- Added SearchResults dropdown component
- Updated DashboardHeader to implement search functionality
- Implemented keyboard navigation and filtering by category
- Created Zustand store for search state
- Added debounced search functionality
- Implemented result filtering and error handling
- Update SearchResults to pass folder information when navigating to notes
- Modify NotesPageLayout to automatically open the correct folder
- Ensure selected note is displayed immediately after search
- Improve user workflow by reducing clicks needed to access notes

This change ensures that when users click on a note in search results,
they're taken directly to the note with its containing folder opened.
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jun 6, 2025

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Walkthrough

A global search feature was implemented across the client and server. The client now includes an interactive search field in the dashboard header, a dropdown component for categorized results, a Zustand store for search state, and a debounced search service. The server exposes a new /api/search endpoint, controller, and model indexes for efficient querying.

Changes

File(s) Change Summary
client/package.json Added lodash.debounce dependency.
client/src/components/DashboardHeader.jsx Enhanced with interactive search field, keyboard navigation, and integrated search results dropdown.
client/src/components/SearchResults.jsx New component for displaying categorized search results with navigation and click handling.
client/src/features/Notes/components/layout/NotesPageLayout.jsx Improved handling of navigation state for opening specific notes/folders from search results.
client/src/services/searchService.js Added globalSearch function to call the backend search API.
client/src/stores/useSearchStore.js New Zustand store for managing search state, debounced queries, and result navigation.
server/app.js Registered new /api/search route.
server/controllers/searchController.js Added searchAll controller for global search across tasks, notes, and habits.
server/models/habitModel.js, server/models/noteModel.js, server/models/taskModel.js Added text and compound indexes to optimize search and sorting by user/update time.
server/routes/searchRoutes.js New route module for /api/search with authentication and OpenAPI docs.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant DashboardHeader
    participant useSearchStore
    participant searchService
    participant API_Server
    participant searchController
    participant DB

    User->>DashboardHeader: Types in search field
    DashboardHeader->>useSearchStore: search(query)
    useSearchStore->>searchService: globalSearch(query, options) (debounced)
    searchService->>API_Server: GET /api/search?q=...&type=...
    API_Server->>searchController: Handle /api/search
    searchController->>DB: Query tasks, notes, habits (text search)
    DB-->>searchController: Return results
    searchController-->>API_Server: Aggregate and return JSON
    API_Server-->>searchService: Respond with results
    searchService-->>useSearchStore: Return results
    useSearchStore-->>DashboardHeader: Update results
    DashboardHeader-->>User: Show categorized dropdown and navigation
Loading

Assessment against linked issues

Objective (Issue #) Addressed Explanation
Implement functional dashboard header search (dashboard header search field is not functional yet) (#33)

Assessment against linked issues: Out-of-scope changes

No out-of-scope changes found.

Poem

A search bar now sparkles atop the dash,
With keyboard and clicks, you’ll find in a flash!
Notes, tasks, and habits—results all around,
In dropdowns and tabs, your queries are found.
🐇✨ “Hop to it!” says Rabbit, “Your search dreams come true—
With code and some carrots, we built it for you!”


🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Explain this complex logic.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai explain this code block.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and explain its main purpose.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@ihabadham
Copy link
Owner Author

@coderabbitai review

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jun 6, 2025

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

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

🧹 Nitpick comments (9)
client/package.json (1)

59-61: Consider leveraging existing lodash import instead of adding a new package
Since lodash is already a dependency, you can import debounce via import debounce from 'lodash/debounce' which will tree-shake unused code instead of adding lodash.debounce. This reduces dependency surface and simplifies version management.

server/models/taskModel.js (1)

54-57: Add background indexing to avoid write blocks
Creating text and compound indexes can lock the collection. Consider adding { background: true } to both indexes so they build without blocking writes in production.

server/models/habitModel.js (1)

70-73: Background index creation recommended
For the new text and user/updateTime indexes, specify { background: true } in habitSchema.index(...) to prevent collection locks during index builds.

server/models/noteModel.js (1)

52-56: Use background indexing and consider index weights
Add { background: true } to each new index, and if certain fields (e.g., title over content) are more important in search results, consider specifying weights in your text index.

client/src/services/searchService.js (1)

23-23: Consider response data consistency.

The function returns the full response object instead of response.data. This might be inconsistent with other service functions that typically return just the data payload. Verify if consumers expect the full response or just the data.

If consistency with other services is desired:

-    return response;
+    return response.data;
client/src/components/SearchResults.jsx (1)

86-86: Consider adding max-width for better mobile responsiveness.

The dropdown uses full width which might be too wide on larger screens.

-      className="absolute top-full left-0 right-0 mt-1 bg-background border border-border rounded-md shadow-md max-h-[70vh] overflow-y-auto z-50 p-2"
+      className="absolute top-full left-0 right-0 mt-1 bg-background border border-border rounded-md shadow-md max-h-[70vh] max-w-2xl overflow-y-auto z-50 p-2"
server/controllers/searchController.js (1)

82-84: Improve HTML tag removal regex for better security.

The current regex for removing HTML tags is basic and might miss some edge cases or malformed HTML.

-            const plainTextContent = note.content
-              ? note.content.replace(/<[^>]*>?/gm, "") // Remove HTML tags
-              : "";
+            const plainTextContent = note.content
+              ? note.content.replace(/<[^>]*>/g, "").replace(/&[^;]+;/g, " ") // Remove HTML tags and entities
+              : "";

Or consider using a proper HTML sanitization library like striptags for more robust HTML removal.

client/src/stores/useSearchStore.js (2)

7-30: Consider cleanup for debounced function on store destruction.

The debounced function doesn't have cleanup which could lead to memory leaks if the store is destroyed while pending searches exist.

Consider adding cleanup logic:

 export const useSearchStore = create((set, get) => {
   // Create a debounced search function
   const debouncedSearchFn = debounce(async (searchQuery, selectedCategory) => {
     if (!searchQuery.trim()) {
       set({ results: null, isOpen: false, isSearching: false });
       return;
     }

     try {
       const results = await globalSearch(searchQuery, {
         type: selectedCategory !== "all" ? selectedCategory : "all",
       });

       set({
         results,
         isSearching: false,
         isOpen: true,
       });
     } catch (error) {
       console.error("Search failed:", error);
       set({
         isSearching: false,
         error: "Failed to perform search. Please try again.",
       });
     }
   }, 300);

+  // Add cleanup method to the store
+  const cleanup = () => {
+    debouncedSearchFn.cancel();
+  };

23-29: Consider preserving isOpen state on search error.

Currently, search errors don't explicitly set isOpen: false, which might leave the dropdown open showing an error. This could be the intended behavior, but consider if it should close on error.

If errors should close the dropdown:

     } catch (error) {
       console.error("Search failed:", error);
       set({
         isSearching: false,
+        isOpen: false,
         error: "Failed to perform search. Please try again.",
       });
     }
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ee76dc0 and fb5456e.

⛔ Files ignored due to path filters (1)
  • client/package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (12)
  • client/package.json (1 hunks)
  • client/src/components/DashboardHeader.jsx (1 hunks)
  • client/src/components/SearchResults.jsx (1 hunks)
  • client/src/features/Notes/components/layout/NotesPageLayout.jsx (2 hunks)
  • client/src/services/searchService.js (1 hunks)
  • client/src/stores/useSearchStore.js (1 hunks)
  • server/app.js (2 hunks)
  • server/controllers/searchController.js (1 hunks)
  • server/models/habitModel.js (1 hunks)
  • server/models/noteModel.js (1 hunks)
  • server/models/taskModel.js (1 hunks)
  • server/routes/searchRoutes.js (1 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (3)
client/src/features/Notes/components/layout/NotesPageLayout.jsx (3)
client/src/features/Notes/hooks/useNoteQueries.js (2)
  • useNoteQuery (75-87)
  • useNoteQuery (75-87)
client/src/features/Notes/pages/NotesPage.jsx (1)
  • location (18-18)
client/src/features/Notes/components/dashboard/NotesDashboardPanel.jsx (1)
  • folders (57-57)
client/src/services/searchService.js (1)
client/src/services/api/apiClient.js (1)
  • apiClient (5-12)
server/controllers/searchController.js (1)
server/utils/asyncHandler.js (1)
  • asyncHandler (8-12)
⏰ Context from checks skipped due to timeout of 90000ms (1)
  • GitHub Check: Test Backend
🔇 Additional comments (15)
server/app.js (1)

16-16: Ensure search endpoint is secured and documented
You’ve correctly wired searchRoutes under /api/search, but please verify that the route applies the same authentication/middleware as other user-scoped endpoints. Also confirm that Swagger docs include the new /api/search operations.

Also applies to: 141-142

client/src/features/Notes/components/layout/NotesPageLayout.jsx (2)

59-67: LGTM! Clean folder navigation logic.

The folder selection logic properly checks if the folder exists in the fetched folders list before setting it as current, preventing invalid folder states.


39-39: LGTM! Clear documentation of search integration.

The updated comments accurately reflect that navigation state can now come from both dashboard and search functionality.

Also applies to: 69-69

client/src/services/searchService.js (2)

3-10: LGTM! Excellent JSDoc documentation.

The function documentation is comprehensive and clearly describes parameters, options, and return types.


24-27: LGTM! Proper error handling.

Error logging and re-throwing allows for appropriate error handling at the consumer level.

client/src/components/DashboardHeader.jsx (3)

15-31: LGTM! Comprehensive keyboard navigation.

The keyboard event handling properly supports arrow key navigation, escape to blur, and prevents default behaviors appropriately. The logic integrates well with the search store.


39-39: LGTM! Proper positioning for dropdown.

Adding relative positioning to the container is necessary for the SearchResults dropdown to position correctly.


42-48: LGTM! Clean search integration.

The input ref and event handlers are properly connected to the search store functionality.

server/routes/searchRoutes.js (3)

7-62: LGTM! Excellent Swagger documentation.

The API documentation is comprehensive, covering all parameters, response schemas, and error cases. This will be valuable for API consumers and maintainers.


63-63: LGTM! Proper authentication and routing.

The route correctly applies the protect middleware for authentication and delegates to the search controller.


1-5: LGTM! Clean module structure.

Standard Express router setup with appropriate imports and controller delegation.

client/src/components/SearchResults.jsx (2)

26-36: LGTM! Good click-outside implementation.

The click-outside handler is properly implemented with cleanup in the useEffect dependency array.


80-81: ⚠️ Potential issue

Fix currentIndex mutation during render.

The currentIndex variable is being mutated during the render phase, which violates React's principle of pure render functions and can lead to inconsistent behavior.

Consider computing the index ranges upfront or using a more functional approach:

-  // Calculate current index for keyboard navigation
-  let currentIndex = -1;
+  // Calculate item indices for keyboard navigation
+  const getItemIndex = () => {
+    const categories = [];
+    if (selectedCategory === "all" || selectedCategory === "tasks") {
+      categories.push({ type: 'tasks', items: results?.tasks || [] });
+    }
+    if (selectedCategory === "all" || selectedCategory === "notes") {
+      categories.push({ type: 'notes', items: results?.notes || [] });
+    }
+    if (selectedCategory === "all" || selectedCategory === "habits") {
+      categories.push({ type: 'habits', items: results?.habits || [] });
+    }
+    
+    let currentIndex = -1;
+    return () => ++currentIndex;
+  };
+  
+  const getNextIndex = getItemIndex();

Likely an incorrect or invalid review comment.

server/controllers/searchController.js (1)

57-70: LGTM! Good parallel execution implementation.

The parallel search execution using Promise.all is well implemented and efficient.

client/src/stores/useSearchStore.js (1)

67-91: LGTM! Well-implemented keyboard navigation logic.

The navigation logic correctly handles cycling through results and respects category filtering. The modulo arithmetic ensures smooth wrapping between first and last items.

cursor[bot]

This comment was marked as outdated.

ihabadham added 5 commits June 6, 2025 04:38
Replace mutable index counter with functional index generator in SearchResults component.

This change addresses CodeRabbit AI feedback by:
- Eliminating direct mutations during render
- Using a closure-based approach for sequential index generation
- Making the code more predictable and maintainable
- Better aligning with React's functional paradigm

The functionality remains identical, but the implementation is now more robust.
…ry call as per coderabbit review

The change removes a redundant enabled condition that was passed as a
second argument to the useNoteQuery hook in NotesPageLayout.jsx.
The hook implementation already includes the proper enabled condition
internally (enabled: !!id && !!userId), making the additional condition unnecessary.
…dexes - as per coderabbit suggestion

Replace case-insensitive regex searches with MongoDB's native text search capabilities
to improve search performance and relevance. This change:
- Utilizes existing text indexes in the models
- Adds relevance scoring to sort results by match quality
- Improves performance for large datasets
- Simplifies search condition code with a unified baseCondition
…as per coderabbit suggestion

Add validation for search API parameters to improve security and prevent misuse:
- Limit the maximum number of results to 100
- Validate the 'type' parameter against a list of allowed values
…oderabbit suggestion

Reset the keyboard navigation index when starting a new search to ensure
consistent user experience.
This prevents highlighting stale results from previous searches and
ensures that keyboard navigation always starts fresh with each new query,
avoiding potential out-of-bounds errors when search results length changes.
cursor[bot]

This comment was marked as outdated.

- Fixed issue where focused items would jump or focus incorrectly
  due to unstable indexes generated on each render.
- Implemented stable indexing with a memoized flattened results array.
- Added Enter key support for navigating to focused items.
- Addressed from Cursor BugBot bug report.
cursor[bot]

This comment was marked as resolved.

@ihabadham ihabadham merged commit 6a812e8 into develop Jun 6, 2025
6 checks passed
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.

1 participant