Skip to content

Comments

Add Pagefind docs search to left sidebar#2074

Closed
ComputelessComputer wants to merge 5 commits intomainfrom
devin/1764682496-add-pagefind-docs-search
Closed

Add Pagefind docs search to left sidebar#2074
ComputelessComputer wants to merge 5 commits intomainfrom
devin/1764682496-add-pagefind-docs-search

Conversation

@ComputelessComputer
Copy link
Collaborator

@ComputelessComputer ComputelessComputer commented Dec 2, 2025

Add Pagefind docs search to left sidebar

Summary

Adds static search functionality to the docs using Pagefind. The search is accessible via a button in the left sidebar that opens a command palette (using cmdk), or by pressing Cmd+K / Ctrl+K.

Changes:

  • Added pagefind, vite-plugin-pagefind, cmdk, and dompurify as dependencies
  • Modified build script to run Pagefind indexing after vite build, targeting only docs pages
  • Created DocsSearch component with command palette UI and keyboard shortcut support
  • Integrated the search component into the docs left sidebar
  • Added **/pagefind/ to .gitignore

Updates since last revision

  • Refactored DocsSearch to use cmdk command palette instead of inline search input. Clicking the search button or pressing Cmd+K opens a modal dialog with search results.
  • Added DOMPurify to sanitize HTML excerpts before rendering (addresses XSS concern flagged by CodeRabbit)
  • Added pagefind generated files to .gitignore

Review & Testing Checklist for Human

  • Test command palette opens: Click the "Search docs..." button in the sidebar and verify the command palette dialog appears
  • Test Cmd+K hotkey: Press Cmd+K (Mac) or Ctrl+K (Windows/Linux) on a docs page and verify the search palette opens
  • Test search functionality: Type a query (e.g., "analytics"), verify results appear with highlighted excerpts
  • Verify result navigation: Click a search result and confirm it navigates to the correct doc page
  • Verify hotkey doesn't trigger in inputs: Focus a text input on the page and press Cmd+K - it should NOT open the search palette

Recommended test plan: Open the preview deployment at https://deploy-preview-2074--hyprnote.netlify.app/docs, test both the button click and Cmd+K hotkey to open search, search for terms like "analytics" or "extension", click a result and confirm navigation works.

Notes

  • Search is intentionally disabled during development (fails silently) since the pagefind index only exists after build
  • Uses dangerouslySetInnerHTML for excerpts because Pagefind returns HTML with highlighted search terms - now sanitized with DOMPurify
  • Pagefind is loaded via dynamic import from /pagefind/pagefind.js which only exists after build completes

Link to Devin run: https://app.devin.ai/sessions/c35a8e838c334d5995fd562322863989
Requested by: john@hyprnote.com (@ComputelessComputer)

Co-Authored-By: john@hyprnote.com <john@hyprnote.com>
@devin-ai-integration
Copy link
Contributor

🤖 Devin AI Engineer

I'll be helping with this pull request! Here's what you should know:

✅ I will automatically:

  • Address comments on this PR that start with 'DevinAI' or '@devin'.
  • Look at CI failures and help fix them

Note: I can only respond to comments from users who have write access to this repository.

⚙️ Control Options:

  • Disable automatic comment and CI monitoring

@netlify
Copy link

netlify bot commented Dec 2, 2025

Deploy Preview for hyprnote-storybook ready!

Name Link
🔨 Latest commit f707ca7
🔍 Latest deploy log https://app.netlify.com/projects/hyprnote-storybook/deploys/692f0ffc91f12d0007ca1c4f
😎 Deploy Preview https://deploy-preview-2074--hyprnote-storybook.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

@netlify
Copy link

netlify bot commented Dec 2, 2025

Deploy Preview for hyprnote failed.

Name Link
🔨 Latest commit f707ca7
🔍 Latest deploy log https://app.netlify.com/projects/hyprnote/deploys/692f0ffc182a7f0008b2888a

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Dec 2, 2025

📝 Walkthrough

Walkthrough

Adds client-side docs search: new React DocsSearch component (lazy-loading Pagefind) integrated into the docs sidebar, Vite plugin and build-step to generate Pagefind indexes, package dependency additions, and .gitignore entries for Pagefind output.

Changes

Cohort / File(s) Summary
Build manifest
apps/web/package.json
Updated build script to run vite build && pagefind --site dist/client --glob \"docs/**/*.html\". Added runtime deps cmdk@1.1.1, dompurify@^3.3.0; added dev deps pagefind@^1.4.0, vite-plugin-pagefind@^1.0.7, and @types/dompurify@^3.2.0.
Vite config / plugin
apps/web/vite.config.ts
Imported vite-plugin-pagefind and registered the Pagefind plugin with options (outputDirectory, buildScript, developStrategy).
Search component
apps/web/src/components/docs-search.tsx
Added DocsSearch React component: dynamically imports/initializes Pagefind, manages open/query/results/loading state, supports Cmd/Ctrl+K toggle, sanitizes excerpts (uses DOMPurify), shows loading/empty states, renders up to 10 results, and navigates to selected result.
Docs route integration
apps/web/src/routes/_view/docs/route.tsx
Imported and rendered <DocsSearch /> inside the LeftSidebar above DocsNavigation.
Ignore rules
.gitignore
Added ignore block for Pagefind output directories using **/pagefind/ pattern.

Sequence Diagram(s)

mermaid
sequenceDiagram
participant User as User
participant UI as DocsSearch (Browser)
participant PF as Pagefind (dynamic import / worker)
participant Nav as Router

User->>UI: Ctrl/Cmd+K or click -> open search
UI->>PF: dynamic import & init (if not loaded)
PF-->>UI: initialization complete / index ready
User->>UI: type query
UI->>PF: search(query)
PF-->>UI: return results
UI->>Nav: user selects result (navigate)
Nav-->>User: navigate to result URL

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

  • Inspect lifecycle/cleanup in apps/web/src/components/docs-search.tsx (dynamic import, event listeners, worker termination).
  • Verify DOMPurify usage and sanitization config before injecting excerpts.
  • Confirm apps/web/package.json build script paths match the actual dist layout and Vite pagefind plugin options.

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 title accurately and concisely describes the main change: adding a Pagefind search feature to the left sidebar of the docs layout.
Description check ✅ Passed The pull request description clearly outlines the addition of Pagefind docs search functionality, detailing all changes made including dependencies, build modifications, new components, and integration points.
✨ 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 devin/1764682496-add-pagefind-docs-search

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

🧹 Nitpick comments (1)
apps/web/src/components/docs-search.tsx (1)

6-23: Consider inlining interfaces (optional).

Per coding guidelines, interfaces that aren't shared could be inlined. However, since these types document the Pagefind API structure, keeping them separate may aid clarity.

If you prefer to follow the guideline strictly, you could inline them:

const [pagefind, setPagefind] = useState<{
  search: (query: string) => Promise<{
    results: Array<{
      id: string;
      data: () => Promise<{
        url: string;
        meta: { title: string };
        excerpt: string;
      }>;
    }>;
  } | null>;
} | null>(null);

However, the current approach is more readable.

📜 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 1eee019 and d5cde9f.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (3)
  • apps/web/package.json (2 hunks)
  • apps/web/src/components/docs-search.tsx (1 hunks)
  • apps/web/src/routes/_view/docs/route.tsx (2 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{ts,tsx}: Avoid creating a bunch of types/interfaces if they are not shared. Especially for function props, just inline them instead.
Never do manual state management for form/mutation. Use useForm (from tanstack-form) and useQuery/useMutation (from tanstack-query) instead for 99% of cases. Avoid patterns like setError.
If there are many classNames with conditional logic, use cn (import from @hypr/utils). It is similar to clsx. Always pass an array and split by logical grouping.
Use motion/react instead of framer-motion.

Files:

  • apps/web/src/components/docs-search.tsx
  • apps/web/src/routes/_view/docs/route.tsx
🧠 Learnings (1)
📚 Learning: 2025-11-24T16:32:24.348Z
Learnt from: CR
Repo: fastrepl/hyprnote PR: 0
File: apps/desktop-e2e/AGENTS.md:0-0
Timestamp: 2025-11-24T16:32:24.348Z
Learning: Applies to apps/desktop-e2e/**/*.{test,spec}.{js,ts,tsx} : Refer to `scripts/setup-desktop-e2e.sh` for end-to-end test environment setup if setup is missing

Applied to files:

  • apps/web/package.json
🧬 Code graph analysis (2)
apps/web/src/components/docs-search.tsx (1)
packages/utils/src/cn.ts (1)
  • cn (20-22)
apps/web/src/routes/_view/docs/route.tsx (1)
apps/web/src/components/docs-search.tsx (1)
  • DocsSearch (25-158)
🪛 ast-grep (0.40.0)
apps/web/src/components/docs-search.tsx

[warning] 142-142: Usage of dangerouslySetInnerHTML detected. This bypasses React's built-in XSS protection. Always sanitize HTML content using libraries like DOMPurify before injecting it into the DOM to prevent XSS attacks.
Context: dangerouslySetInnerHTML
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation [REFERENCES]
- https://reactjs.org/docs/dom-elements.html#dangerouslysetinnerhtml
- https://cwe.mitre.org/data/definitions/79.html

(react-unsafe-html-injection)

🪛 Biome (2.1.2)
apps/web/src/components/docs-search.tsx

[error] 143-143: Avoid passing content using the dangerouslySetInnerHTML prop.

Setting content using code can expose users to cross-site scripting (XSS) attacks

(lint/security/noDangerouslySetInnerHtml)

🪛 GitHub Actions: web_ci
apps/web/src/components/docs-search.tsx

[error] 1-1: vite: Rollup failed to resolve import '/pagefind/pagefind.js' from docs-search.tsx. This may break runtime; consider externalizing the module or ensuring the path exists.

⏰ 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: Devin
🔇 Additional comments (4)
apps/web/package.json (2)

7-7: LGTM! Build script correctly chains Pagefind indexing.

The updated build script properly sequences the Vite build before running Pagefind indexing, ensuring the HTML files exist before indexing. The glob pattern appropriately targets only docs HTML files.


80-80: Version 1.4.0 is the latest stable release (as of September 2025). The dependency is correctly placed in devDependencies with an appropriate caret range for patch-level updates.

apps/web/src/routes/_view/docs/route.tsx (2)

10-10: LGTM! Clean import statement.

The import follows the project's path alias conventions and uses a named export appropriately.


83-83: LGTM! Appropriate component placement.

The DocsSearch component is correctly positioned in the sidebar before the navigation menu, providing a logical search-then-navigate flow.

devin-ai-integration bot and others added 2 commits December 2, 2025 13:49
Co-Authored-By: john@hyprnote.com <john@hyprnote.com>
Co-Authored-By: john@hyprnote.com <john@hyprnote.com>
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 (3)
apps/web/src/components/docs-search.tsx (3)

117-184: Add keyboard navigation for accessibility.

The search dropdown still lacks keyboard navigation (arrow keys to navigate results, Enter to select, Escape to close), which prevents keyboard-only users from using the search feature and violates WCAG accessibility standards.

Implement keyboard support with state for selected index:

 export function DocsSearch() {
   const [query, setQuery] = useState("");
   const [results, setResults] = useState<PagefindResult[]>([]);
   const [isOpen, setIsOpen] = useState(false);
   const [isLoading, setIsLoading] = useState(false);
   const [pagefind, setPagefind] = useState<PagefindInstance | null>(null);
+  const [selectedIndex, setSelectedIndex] = useState(-1);
   const inputRef = useRef<HTMLInputElement>(null);
   const containerRef = useRef<HTMLDivElement>(null);
+
+  const handleKeyDown = (e: React.KeyboardEvent) => {
+    if (!isOpen || results.length === 0) return;
+    
+    switch (e.key) {
+      case 'ArrowDown':
+        e.preventDefault();
+        setSelectedIndex(prev => prev < results.length - 1 ? prev + 1 : 0);
+        break;
+      case 'ArrowUp':
+        e.preventDefault();
+        setSelectedIndex(prev => prev > 0 ? prev - 1 : results.length - 1);
+        break;
+      case 'Enter':
+        e.preventDefault();
+        if (selectedIndex >= 0 && results[selectedIndex]) {
+          window.location.href = results[selectedIndex].url;
+          setIsOpen(false);
+        }
+        break;
+      case 'Escape':
+        e.preventDefault();
+        setIsOpen(false);
+        break;
+    }
+  };

Apply to the input:

 <input
   ref={inputRef}
   type="text"
   value={query}
   onChange={(e) => handleSearch(e.target.value)}
   onFocus={() => query && setIsOpen(true)}
+  onKeyDown={handleKeyDown}
   placeholder="Search docs..."

And highlight the selected result:

 <a
   href={result.url}
   onClick={() => setIsOpen(false)}
   className={cn([
     "block px-4 py-3 hover:bg-neutral-50 transition-colors",
     "border-b border-neutral-100 last:border-b-0",
+    selectedIndex === index && "bg-neutral-100",
   ])}
 >

170-170: Sanitize HTML before rendering with dangerouslySetInnerHTML.

The excerpt HTML from Pagefind is rendered without sanitization, creating an XSS vulnerability. Even though Pagefind should sanitize during indexing, defense-in-depth requires sanitization before rendering.

Install DOMPurify:

npm install dompurify
npm install --save-dev @types/dompurify

Then sanitize the excerpt:

+import DOMPurify from 'dompurify';
+
 // ... in the component:
 <div
   className="text-xs text-neutral-500 mt-1 line-clamp-2"
-  dangerouslySetInnerHTML={{ __html: result.excerpt }}
+  dangerouslySetInnerHTML={{ __html: DOMPurify.sanitize(result.excerpt) }}
 />

156-156: Use unique identifier for result keys instead of array index.

Using array index as key can cause React reconciliation issues when search results change. Use result.url which should be unique per result.

-{results.map((result, index) => (
-  <li key={index}>
+{results.map((result) => (
+  <li key={result.url}>
🧹 Nitpick comments (1)
apps/web/src/components/docs-search.tsx (1)

88-115: Consider logging search errors for debugging.

The search handler silently catches and ignores errors (line 108-109). While the PR description mentions "fails silently with console.warn", no warning is actually logged. Adding a console.warn or console.error would improve debugging when Pagefind fails to load or search errors occur.

Apply this diff to add error logging:

-      } catch {
+      } catch (error) {
+        console.warn('Pagefind search failed:', error);
         setResults([]);
       } finally {
📜 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 72d1ba9 and d1655a0.

📒 Files selected for processing (1)
  • apps/web/src/components/docs-search.tsx (1 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{ts,tsx}: Avoid creating a bunch of types/interfaces if they are not shared. Especially for function props, just inline them instead.
Never do manual state management for form/mutation. Use useForm (from tanstack-form) and useQuery/useMutation (from tanstack-query) instead for 99% of cases. Avoid patterns like setError.
If there are many classNames with conditional logic, use cn (import from @hypr/utils). It is similar to clsx. Always pass an array and split by logical grouping.
Use motion/react instead of framer-motion.

Files:

  • apps/web/src/components/docs-search.tsx
🧬 Code graph analysis (1)
apps/web/src/components/docs-search.tsx (1)
packages/utils/src/cn.ts (1)
  • cn (20-22)
🪛 ast-grep (0.40.0)
apps/web/src/components/docs-search.tsx

[warning] 169-169: Usage of dangerouslySetInnerHTML detected. This bypasses React's built-in XSS protection. Always sanitize HTML content using libraries like DOMPurify before injecting it into the DOM to prevent XSS attacks.
Context: dangerouslySetInnerHTML
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation [REFERENCES]
- https://reactjs.org/docs/dom-elements.html#dangerouslysetinnerhtml
- https://cwe.mitre.org/data/definitions/79.html

(react-unsafe-html-injection)

🪛 Biome (2.1.2)
apps/web/src/components/docs-search.tsx

[error] 170-170: Avoid passing content using the dangerouslySetInnerHTML prop.

Setting content using code can expose users to cross-site scripting (XSS) attacks

(lint/security/noDangerouslySetInnerHtml)

⏰ 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: fmt
  • GitHub Check: Redirect rules - hyprnote
  • GitHub Check: Header rules - hyprnote
  • GitHub Check: Pages changed - hyprnote
  • GitHub Check: ci
  • GitHub Check: Devin
🔇 Additional comments (2)
apps/web/src/components/docs-search.tsx (2)

1-29: LGTM!

The imports and interface definitions are appropriate. The interfaces correctly type the external Pagefind API, and the use of cn from @hypr/utils follows the coding guidelines.


40-86: LGTM!

The Pagefind initialization logic correctly handles SSR, checks for existing instances, and uses proper cleanup. The click-outside handler follows standard React patterns with appropriate event listener cleanup.

- Use vite-plugin-pagefind for better dev/build integration
- Use @hypr/ui Command components for command palette UI
- Add Cmd+K keyboard shortcut to open search (only on docs pages)
- Import Pagefind types from vite-plugin-pagefind/types

Co-Authored-By: john@hyprnote.com <john@hyprnote.com>
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

🧹 Nitpick comments (3)
apps/web/src/components/docs-search.tsx (3)

68-91: Consider debouncing search input for better performance.

The search executes on every keystroke, which could cause unnecessary API calls for fast typists. Adding a small debounce (e.g., 150-300ms) would improve performance without impacting UX.

You could use a debounce utility or adjust the implementation:

const handleSearch = useCallback(async (value: string) => {
  setQuery(value);
  if (!value.trim() || !pagefindRef.current) {
    setResults([]);
    return;
  }

  setIsLoading(true);
  try {
    // Add debounce delay here if desired
    const res = await pagefindRef.current.search(value);
    // ... rest of logic
  } catch {
    setResults([]);
  } finally {
    setIsLoading(false);
  }
}, []);

However, since Pagefind is a static client-side search, the performance impact is likely minimal.


147-150: Consider sanitizing HTML excerpts for defense-in-depth security.

While Pagefind generates excerpts from trusted docs at build time (lowering the risk), sanitizing HTML before rendering adds an extra security layer and follows React best practices.

Install DOMPurify:

npm install dompurify
npm install --save-dev @types/dompurify

Then update the component:

+import DOMPurify from 'dompurify';
+
 // ... in the render:
 <div
   className="text-xs text-muted-foreground line-clamp-2"
-  dangerouslySetInnerHTML={{ __html: result.excerpt }}
+  dangerouslySetInnerHTML={{ 
+    __html: DOMPurify.sanitize(result.excerpt, { ALLOWED_TAGS: ['mark', 'b', 'strong', 'em'] }) 
+  }}
 />

Note: Ensure DOMPurify is instantiated client-side only (the component already has SSR guards, so this should be safe).


117-119: Keyboard shortcut hint shows Mac-only symbol.

The ⌘ (Command) symbol is Mac-specific. Consider showing platform-appropriate hints (Ctrl for Windows/Linux).

const isMac = typeof navigator !== 'undefined' && 
  navigator.userAgent.toLowerCase().includes('mac');

// In render:
<span className="text-[11px] rounded border border-neutral-300 px-1.5 py-0.5 text-neutral-400">
  {isMac ? <span className="font-sans">&#8984;</span> : 'Ctrl+'}K
</span>
📜 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 d1655a0 and c040339.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (3)
  • apps/web/package.json (3 hunks)
  • apps/web/src/components/docs-search.tsx (1 hunks)
  • apps/web/vite.config.ts (2 hunks)
🧰 Additional context used
📓 Path-based instructions (3)
**/*.ts

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.ts: Agent implementations should use TypeScript and follow the established architectural patterns defined in the agent framework
Agent communication should use defined message protocols and interfaces

Files:

  • apps/web/vite.config.ts
**/*.config.{ts,json}

📄 CodeRabbit inference engine (CLAUDE.md)

Agent configuration should be centralized and externalized from implementation logic

Files:

  • apps/web/vite.config.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{ts,tsx}: Avoid creating a bunch of types/interfaces if they are not shared. Especially for function props, just inline them instead.
Never do manual state management for form/mutation. Use useForm (from tanstack-form) and useQuery/useMutation (from tanstack-query) instead for 99% of cases. Avoid patterns like setError.
If there are many classNames with conditional logic, use cn (import from @hypr/utils). It is similar to clsx. Always pass an array and split by logical grouping.
Use motion/react instead of framer-motion.

Files:

  • apps/web/vite.config.ts
  • apps/web/src/components/docs-search.tsx
🧠 Learnings (1)
📚 Learning: 2025-11-24T16:32:24.348Z
Learnt from: CR
Repo: fastrepl/hyprnote PR: 0
File: apps/desktop-e2e/AGENTS.md:0-0
Timestamp: 2025-11-24T16:32:24.348Z
Learning: Applies to apps/desktop-e2e/**/*.{test,spec}.{js,ts,tsx} : Refer to `scripts/setup-desktop-e2e.sh` for end-to-end test environment setup if setup is missing

Applied to files:

  • apps/web/package.json
🧬 Code graph analysis (1)
apps/web/src/components/docs-search.tsx (2)
packages/utils/src/cn.ts (1)
  • cn (20-22)
extensions/shared/types/hypr-extension.d.ts (6)
  • CommandDialog (202-202)
  • CommandInput (205-205)
  • CommandList (207-207)
  • CommandEmpty (203-203)
  • CommandGroup (204-204)
  • CommandItem (206-206)
🪛 ast-grep (0.40.0)
apps/web/src/components/docs-search.tsx

[warning] 148-148: Usage of dangerouslySetInnerHTML detected. This bypasses React's built-in XSS protection. Always sanitize HTML content using libraries like DOMPurify before injecting it into the DOM to prevent XSS attacks.
Context: dangerouslySetInnerHTML
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation [REFERENCES]
- https://reactjs.org/docs/dom-elements.html#dangerouslysetinnerhtml
- https://cwe.mitre.org/data/definitions/79.html

(react-unsafe-html-injection)

🪛 Biome (2.1.2)
apps/web/src/components/docs-search.tsx

[error] 149-149: Avoid passing content using the dangerouslySetInnerHTML prop.

Setting content using code can expose users to cross-site scripting (XSS) attacks

(lint/security/noDangerouslySetInnerHtml)

⏰ 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: Redirect rules - hyprnote
  • GitHub Check: Header rules - hyprnote
  • GitHub Check: Pages changed - hyprnote
  • GitHub Check: ci
  • GitHub Check: fmt
  • GitHub Check: Devin
🔇 Additional comments (9)
apps/web/package.json (2)

7-7: Build script correctly chains Pagefind indexing after Vite build.

The command sequence ensures Pagefind only indexes if the build succeeds. Verify that the glob pattern docs/**/*.html captures all prerendered docs pages mentioned in the prerender filter (lines 23-33 in vite.config.ts show docs pages are prerendered).


41-41: The package versions specified are current and secure. cmdk@1.1.1 is the latest stable release with no known vulnerabilities. pagefind@^1.4.0 resolves to version 1.4.0 (the latest stable), which is well above the version threshold (1.1.1+) that fixed CVE-2024-45389. vite-plugin-pagefind@^1.0.7 has no known direct vulnerabilities and its dependency on pagefind is safe with the specified range. No updates required.

apps/web/src/components/docs-search.tsx (5)

1-16: LGTM: Imports are well-organized.

All dependencies are correctly imported, including proper TypeScript types for Pagefind.


18-23: LGTM: State initialization follows React best practices.

The component appropriately uses useState for UI state and useRef for the Pagefind instance reference.


25-43: LGTM: Pagefind loading handles edge cases correctly.

The effect properly handles SSR, prevents race conditions with a cancellation flag, and gracefully degrades when Pagefind is unavailable in development mode.


45-66: LGTM: Keyboard shortcut implementation is robust.

The Cmd/Ctrl+K handler properly checks for conflicting input contexts and prevents default browser behavior. The internal result navigation is handled by the cmdk library components (CommandDialog, CommandItem).


93-98: LGTM: Result selection handler works correctly.

Using window.location.assign for navigation is appropriate here, as it ensures proper page state and hydration for the doc pages.

apps/web/vite.config.ts (2)

8-8: LGTM: Plugin import is correct.

The import aligns with the vite-plugin-pagefind devDependency added in package.json.


40-44: Pagefind plugin configuration is correct.

The configuration options are all valid and properly documented: outputDirectory: "dist/client" correctly points to TanStack Start's prerender output, buildScript: "build" matches the npm build script, and developStrategy: "lazy" is the appropriate indexing strategy for development mode.

Co-Authored-By: john@hyprnote.com <john@hyprnote.com>
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 (2)
apps/web/src/components/docs-search.tsx (2)

69-92: Consider using pagefind.debouncedSearch to avoid stale/overlapping queries

Every keystroke triggers await pagefindRef.current.search(value) (lines 78–85). If the user types quickly, earlier slower searches can resolve after later ones and overwrite newer results, causing unnecessary work on large indexes.

Pagefind exposes a debounced helper that cancels earlier calls:

const search = await pagefind.debouncedSearch(trimmed);
// search === null means a newer query superseded this one
if (!search) return;
// then slice + data() as you're doing now

This reduces redundant work and ensures result ordering stays consistent under rapid typing.


148-152: Resolve the linter complaint around dangerouslySetInnerHTML

You're already sanitizing result.excerpt with DOMPurify before injecting it (Lines 148–152), which is the right mitigation. Pagefind's excerpt field is HTML-escaped and safe for innerHTML with only <mark> tags for highlighting, while content and meta fields are raw and require sanitization. DOMPurify's default sanitize() preserves <mark> tags, so your current approach is both secure and functional.

Add a focused ignore with justification:

+                  {/* Pagefind's excerpt is pre-escaped and then sanitized again via DOMPurify. */}
+                  {/* biome-ignore lint/security/noDangerouslySetInnerHtml: Controlled HTML from Pagefind, sanitized client-side. */}
                   <div
                     className="text-xs text-muted-foreground line-clamp-2"
                     dangerouslySetInnerHTML={{
                       __html: DOMPurify.sanitize(result.excerpt),
                     }}
                   />
📜 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 c040339 and f707ca7.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (3)
  • .gitignore (1 hunks)
  • apps/web/package.json (3 hunks)
  • apps/web/src/components/docs-search.tsx (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • apps/web/package.json
🧰 Additional context used
📓 Path-based instructions (1)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{ts,tsx}: Avoid creating a bunch of types/interfaces if they are not shared. Especially for function props, just inline them instead.
Never do manual state management for form/mutation. Use useForm (from tanstack-form) and useQuery/useMutation (from tanstack-query) instead for 99% of cases. Avoid patterns like setError.
If there are many classNames with conditional logic, use cn (import from @hypr/utils). It is similar to clsx. Always pass an array and split by logical grouping.
Use motion/react instead of framer-motion.

Files:

  • apps/web/src/components/docs-search.tsx
🧬 Code graph analysis (1)
apps/web/src/components/docs-search.tsx (2)
apps/desktop/src/components/main/body/search.tsx (1)
  • Search (12-59)
extensions/shared/types/hypr-extension.d.ts (6)
  • CommandDialog (202-202)
  • CommandInput (205-205)
  • CommandList (207-207)
  • CommandEmpty (203-203)
  • CommandGroup (204-204)
  • CommandItem (206-206)
🪛 ast-grep (0.40.0)
apps/web/src/components/docs-search.tsx

[warning] 149-149: Usage of dangerouslySetInnerHTML detected. This bypasses React's built-in XSS protection. Always sanitize HTML content using libraries like DOMPurify before injecting it into the DOM to prevent XSS attacks.
Context: dangerouslySetInnerHTML
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation [REFERENCES]
- https://reactjs.org/docs/dom-elements.html#dangerouslysetinnerhtml
- https://cwe.mitre.org/data/definitions/79.html

(react-unsafe-html-injection)

🪛 Biome (2.1.2)
apps/web/src/components/docs-search.tsx

[error] 150-150: Avoid passing content using the dangerouslySetInnerHTML prop.

Setting content using code can expose users to cross-site scripting (XSS) attacks

(lint/security/noDangerouslySetInnerHtml)

⏰ 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: Redirect rules - hyprnote
  • GitHub Check: Header rules - hyprnote
  • GitHub Check: Pages changed - hyprnote
  • GitHub Check: ci
  • GitHub Check: fmt
  • GitHub Check: Devin
🔇 Additional comments (2)
.gitignore (1)

24-25: LGTM!

The addition correctly ignores Pagefind-generated index directories using the **/pagefind/ glob pattern, which will match the dist/client/pagefind/ directory and any nested instances. The comment is clear and the placement is appropriate.

apps/web/src/components/docs-search.tsx (1)

19-161: Overall implementation looks solid

The Pagefind lazy-load, Cmd/Ctrl+K hotkey behavior, and CommandDialog-based UI all look clean and align well with the project’s patterns (use of cn, Hypr UI command components, DOMPurify for excerpts, etc.). Aside from the small search-handler tweaks noted above, this component is in good shape.

@yujonglee
Copy link
Contributor

#2075

@yujonglee yujonglee closed this Dec 3, 2025
@yujonglee yujonglee deleted the devin/1764682496-add-pagefind-docs-search branch December 3, 2025 00:04
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