Skip to content

Replaced current search bar with a command/search palette #1072

Merged
yujonglee merged 3 commits intomainfrom
main-cmdk-0704
Jul 5, 2025
Merged

Replaced current search bar with a command/search palette #1072
yujonglee merged 3 commits intomainfrom
main-cmdk-0704

Conversation

@duckduckhero
Copy link
Contributor

[TODOs]

  • make search filters more robust: filter by tag, etc
  • allow Hyprnote features to be searched as well (settings, templates, etc)

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jul 4, 2025

📝 Walkthrough

Walkthrough

A new Command Palette feature was introduced to the desktop app, including a new dependency and a React component for a multi-entity search dialog. The search provider and search bar were updated to integrate the Command Palette, and the sessions database search query was expanded to include additional fields and improved result ordering.

Changes

File(s) Change Summary
apps/desktop/package.json Added "cmdk": "1.1.1" to dependencies.
apps/desktop/src/components/command-palette.tsx Added new exported CommandPalette React component for a unified, multi-entity search modal dialog.
apps/desktop/src/components/search-bar.tsx Integrated CommandPalette; replaced search focus logic with state to control palette visibility.
apps/desktop/src/contexts/search.tsx Added state and hotkey logic to control CommandPalette visibility; rendered CommandPalette in provider.
crates/db-user/src/sessions_ops.rs Expanded session search SQL to include memo fields, stripped HTML, and improved result ordering by title match and creation date.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant SearchBar
    participant CommandPalette
    participant DB
    participant Router

    User->>SearchBar: Clicks search bar or presses Mod+K
    SearchBar->>CommandPalette: Set open=true
    CommandPalette->>User: Shows modal, focuses input
    User->>CommandPalette: Types search query
    CommandPalette->>DB: Debounced multi-entity search (sessions, events, people, orgs)
    DB-->>CommandPalette: Returns grouped results
    CommandPalette->>User: Displays results with highlights
    User->>CommandPalette: Selects result
    CommandPalette->>Router: Navigate to selected entity route
    CommandPalette->>CommandPalette: Close modal, clear state
Loading
sequenceDiagram
    participant CommandPalette
    participant DB

    CommandPalette->>DB: Search sessions with query
    DB->>DB: Strip HTML from memo fields
    DB->>DB: Match query in title, enhanced_memo_html, raw_memo_html
    DB->>DB: Order by title match priority, then by created_at desc
    DB-->>CommandPalette: Return ordered session results
Loading

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 Clippy (1.86.0)
Updating git repository `https://github.com/RustAudio/cpal`

error: failed to load source for dependency cpal

Caused by:
Unable to update https://github.com/RustAudio/cpal?rev=51c3b43#51c3b43c

Caused by:
failed to create directory /usr/local/git/db/cpal-476cd1dd23dbc279

Caused by:
Permission denied (os error 13)

✨ Finishing Touches
  • 📝 Generate Docstrings

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
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

‼️ IMPORTANT
Auto-reply has been disabled for this repository in the CodeRabbit settings. The CodeRabbit bot will not respond to your replies unless it is explicitly tagged.

  • 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.

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.

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

🧹 Nitpick comments (3)
apps/desktop/src/components/search-bar.tsx (1)

124-124: Consider the UX implications of keeping the search input interactive.

The search bar still allows text input and focus, but clicking the container opens the command palette. This dual behavior might confuse users who type directly in the search input expecting it to work.

Consider either:

  1. Making the entire search bar read-only and act purely as a button to open the command palette
  2. Removing the onClick handler from the container and only opening the palette via the keyboard shortcut

For option 1:

       <input
         ref={searchInputRef}
         type="text"
         value={searchQuery}
         onChange={handleInputChange}
         onKeyDown={handleKeyDown}
         onFocus={() => {
           setIsFocused(true);
           setShowHistory(searchQuery === "");
         }}
         onBlur={() => {
           setTimeout(() => {
             setIsFocused(false);
             setShowHistory(false);
           }, 150);
         }}
         placeholder={t`Search...`}
         className="flex-1 bg-transparent outline-none text-xs"
+        readOnly
+        style={{ cursor: 'pointer' }}
       />

Also applies to: 129-147

crates/db-user/src/sessions_ops.rs (1)

125-142: Consider performance implications of the expanded search query.

The implementation correctly expands search to memo fields, but there are performance considerations:

  1. Multiple REPLACE operations on potentially large HTML fields could be slow
  2. LIKE queries with leading wildcards prevent index usage

Consider these optimizations:

  1. Add a full-text search index on searchable fields
  2. Pre-process and store plain text versions of HTML content
  3. Use database-specific full-text search capabilities (e.g., SQLite FTS5)
apps/desktop/src/components/command-palette.tsx (1)

192-208: Consider the impact of global style injection.

The injected styles affect all modal dialogs, not just the command palette.

Consider a more targeted approach:

  1. Add a unique class or data attribute to the CommandDialog
  2. Use CSS modules or styled-components for scoped styles
  3. Check if the cmdk library provides width configuration options
📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 4a4d77b and f3da26d.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (5)
  • apps/desktop/package.json (1 hunks)
  • apps/desktop/src/components/command-palette.tsx (1 hunks)
  • apps/desktop/src/components/search-bar.tsx (4 hunks)
  • apps/desktop/src/contexts/search.tsx (3 hunks)
  • crates/db-user/src/sessions_ops.rs (1 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
`**/*.{js,ts,tsx,rs}`: 1. No error handling. 2. No unused imports, variables, or functions. 3. For comments, keep it minimal. It should be about "Why", not "What".

**/*.{js,ts,tsx,rs}: 1. No error handling.
2. No unused imports, variables, or functions.
3. For comments, keep it minimal. It should be about "Why", not "What".

⚙️ Source: CodeRabbit Configuration File

List of files the instruction was applied to:

  • crates/db-user/src/sessions_ops.rs
  • apps/desktop/src/contexts/search.tsx
  • apps/desktop/src/components/search-bar.tsx
  • apps/desktop/src/components/command-palette.tsx
🧬 Code Graph Analysis (2)
apps/desktop/src/contexts/search.tsx (1)
apps/desktop/src/components/command-palette.tsx (1)
  • CommandPalette (92-393)
apps/desktop/src/components/search-bar.tsx (1)
apps/desktop/src/components/command-palette.tsx (1)
  • CommandPalette (92-393)
⏰ Context from checks skipped due to timeout of 90000ms (3)
  • GitHub Check: ci
  • GitHub Check: ci (windows, windows-latest)
  • GitHub Check: ci (macos, macos-latest)
🔇 Additional comments (5)
apps/desktop/package.json (1)

72-72: cmdk@1.1.1 version verified – no action needed

cmdk@1.1.1 is the latest stable release and has no known direct vulnerabilities (per Snyk). Pinning this version is fine for reproducibility. For ongoing security, continue to:

  • Audit indirect dependencies of cmdk
  • Integrate tooling (e.g., Snyk, Dependabot) to catch new vulnerabilities as they arise
apps/desktop/src/contexts/search.tsx (1)

1-1: Clean integration of the CommandPalette!

The implementation properly manages the command palette state and simplifies the mod+k hotkey handler.

Also applies to: 6-6, 32-33, 38-38, 110-110

apps/desktop/src/components/command-palette.tsx (3)

45-77: Well-implemented content snippet extraction!

The function properly handles HTML stripping, context extraction, and edge cases.


79-90: Clean sorting implementation.

The date-based sorting logic is correct and handles both sort directions properly.


163-180: Good type-safe navigation handling!

The switch statement properly handles all match types with appropriate navigation.

Comment on lines +128 to +130
REPLACE(REPLACE(REPLACE(enhanced_memo_html, '<', ' '), '>', ' '), '&nbsp;', ' ') LIKE ? OR
REPLACE(REPLACE(REPLACE(raw_memo_html, '<', ' '), '>', ' '), '&nbsp;', ' ') LIKE ?
)
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

HTML entity stripping is incomplete.

The current implementation only handles <, >, and &nbsp;, but other common HTML entities like &amp;, &quot;, &lt;, &gt; are not processed.

Consider a more comprehensive approach:

-REPLACE(REPLACE(REPLACE(enhanced_memo_html, '<', ' '), '>', ' '), '&nbsp;', ' ') LIKE ?
+REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(enhanced_memo_html, '<', ' '), '>', ' '), '&nbsp;', ' '), '&amp;', '&'), '&quot;', '"'), '&lt;', '<'), '&gt;', '>') LIKE ?

Or better yet, implement a proper HTML-to-text conversion function in the application layer before inserting into the database.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
REPLACE(REPLACE(REPLACE(enhanced_memo_html, '<', ' '), '>', ' '), '&nbsp;', ' ') LIKE ? OR
REPLACE(REPLACE(REPLACE(raw_memo_html, '<', ' '), '>', ' '), '&nbsp;', ' ') LIKE ?
)
REPLACE(
REPLACE(
REPLACE(
REPLACE(
REPLACE(
REPLACE(
REPLACE(enhanced_memo_html, '<', ' '),
'>', ' '),
'&nbsp;', ' '),
'&amp;', '&'),
'&quot;', '"'),
'&lt;', '<'),
'&gt;', '>') LIKE ? OR
REPLACE(REPLACE(REPLACE(raw_memo_html, '<', ' '), '>', ' '), '&nbsp;', ' ') LIKE ?
)
🤖 Prompt for AI Agents
In crates/db-user/src/sessions_ops.rs around lines 128 to 130, the current code
replaces only a few HTML entities like '<', '>', and '&nbsp;', missing others
such as '&amp;', '&quot;', '&lt;', and '&gt;'. To fix this, extend the
replacement logic to cover these additional common HTML entities or, preferably,
implement a dedicated HTML-to-text conversion function in the application layer
that fully sanitizes and converts HTML content before it is inserted into the
database, ensuring comprehensive and reliable entity handling.

Comment on lines +183 to +186
const sessionMatches = matches.filter(match => match.type === "session");
const eventMatches = matches.filter(match => match.type === "event");
const humanMatches = matches.filter(match => match.type === "human");
const organizationMatches = matches.filter(match => match.type === "organization");
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

Fix TypeScript type narrowing for filtered matches.

The filter method doesn't properly narrow types, causing a type mismatch with sortSessionMatches.

Use type predicates for proper type narrowing:

-  const sessionMatches = matches.filter(match => match.type === "session");
+  const sessionMatches = matches.filter((match): match is SearchMatch & { type: "session" } => match.type === "session");

Or explicitly type the filtered arrays:

-  const sessionMatches = matches.filter(match => match.type === "session");
+  const sessionMatches = matches.filter(match => match.type === "session") as Array<SearchMatch & { type: "session" }>;

Also applies to: 265-265

🤖 Prompt for AI Agents
In apps/desktop/src/components/command-palette.tsx around lines 183 to 186, the
filter method does not correctly narrow the types of the filtered arrays,
causing type mismatches with functions like sortSessionMatches. To fix this,
define type predicate functions that assert the type of each match and use them
in the filter calls, or explicitly annotate the types of the filtered arrays to
ensure TypeScript understands their narrowed types. Apply the same fix around
line 265 as well.

Comment on lines +136 to +143
// Debounced search
useEffect(() => {
const timeoutId = setTimeout(() => {
performSearch(query);
}, 200);

return () => clearTimeout(timeoutId);
}, [query]);
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

Add missing dependencies to the effect.

The performSearch function uses userId from the component scope, but it's not included in the effect dependencies.

Either add the missing dependencies or use a callback:

   useEffect(() => {
     const timeoutId = setTimeout(() => {
       performSearch(query);
     }, 200);
 
     return () => clearTimeout(timeoutId);
-  }, [query]);
+  }, [query, userId]);

Alternatively, move performSearch inside the effect or wrap it with useCallback.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// Debounced search
useEffect(() => {
const timeoutId = setTimeout(() => {
performSearch(query);
}, 200);
return () => clearTimeout(timeoutId);
}, [query]);
// Debounced search
useEffect(() => {
const timeoutId = setTimeout(() => {
performSearch(query);
}, 200);
return () => clearTimeout(timeoutId);
}, [query, userId]);
🤖 Prompt for AI Agents
In apps/desktop/src/components/command-palette.tsx around lines 136 to 143, the
useEffect hook depends on the performSearch function which uses userId from the
component scope, but userId and performSearch are not included in the dependency
array. To fix this, either add userId and performSearch to the dependencies, or
move performSearch inside the useEffect, or wrap performSearch with useCallback
including userId as a dependency to ensure the effect updates correctly when
userId changes.

Comment on lines +32 to +43
const highlightText = (text: string, query: string) => {
if (!query.trim()) {
return text;
}

const parts = text.split(new RegExp(`(${query})`, "gi"));
return parts.map((part, index) =>
part.toLowerCase() === query.toLowerCase()
? <span key={index} className="font-bold text-neutral-900">{part}</span>
: part
);
};
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

Escape special regex characters in the search query.

The query is used directly in a regex pattern without escaping, which could cause errors if users search for special characters.

 const highlightText = (text: string, query: string) => {
   if (!query.trim()) {
     return text;
   }
 
-  const parts = text.split(new RegExp(`(${query})`, "gi"));
+  const escapedQuery = query.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
+  const parts = text.split(new RegExp(`(${escapedQuery})`, "gi"));
   return parts.map((part, index) =>
-    part.toLowerCase() === query.toLowerCase()
+    part.toLowerCase() === query.toLowerCase()
       ? <span key={index} className="font-bold text-neutral-900">{part}</span>
       : part
   );
 };
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const highlightText = (text: string, query: string) => {
if (!query.trim()) {
return text;
}
const parts = text.split(new RegExp(`(${query})`, "gi"));
return parts.map((part, index) =>
part.toLowerCase() === query.toLowerCase()
? <span key={index} className="font-bold text-neutral-900">{part}</span>
: part
);
};
const highlightText = (text: string, query: string) => {
if (!query.trim()) {
return text;
}
const escapedQuery = query.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
const parts = text.split(new RegExp(`(${escapedQuery})`, "gi"));
return parts.map((part, index) =>
part.toLowerCase() === query.toLowerCase()
? <span key={index} className="font-bold text-neutral-900">{part}</span>
: part
);
};
🤖 Prompt for AI Agents
In apps/desktop/src/components/command-palette.tsx around lines 32 to 43, the
search query is used directly in a regex without escaping special characters,
which can cause errors or unexpected behavior. Fix this by implementing a
function to escape all regex special characters in the query string before using
it in the RegExp constructor. Replace the direct use of query with the escaped
version to ensure safe and correct regex matching.

Comment on lines +129 to +133
} catch (error) {
console.error("Search error:", error);
} finally {
setIsSearching(false);
}
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Improve error handling with user feedback.

Errors are only logged to console without informing the user.

Consider showing an error state:

   } catch (error) {
     console.error("Search error:", error);
+    // Show error state to user
+    setMatches([]);
+    // Consider adding an error state:
+    // setSearchError("Failed to search. Please try again.");
   } finally {
     setIsSearching(false);
   }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
} catch (error) {
console.error("Search error:", error);
} finally {
setIsSearching(false);
}
} catch (error) {
console.error("Search error:", error);
// Show error state to user
setMatches([]);
// Consider adding an error state:
// setSearchError("Failed to search. Please try again.");
} finally {
setIsSearching(false);
}
🤖 Prompt for AI Agents
In apps/desktop/src/components/command-palette.tsx around lines 129 to 133, the
catch block only logs errors to the console without providing user feedback.
Modify the error handling to update the component state with an error message or
flag that can be displayed in the UI, so users are informed when a search error
occurs. Ensure this error state is cleared or reset appropriately after
handling.

@yujonglee yujonglee merged commit 6c3c529 into main Jul 5, 2025
5 checks passed
@yujonglee yujonglee deleted the main-cmdk-0704 branch July 5, 2025 02:11
@yujonglee yujonglee mentioned this pull request Jul 12, 2025
@coderabbitai coderabbitai bot mentioned this pull request Jul 22, 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