Skip to content

Conversation

@fery25
Copy link

@fery25 fery25 commented Nov 28, 2025

Changes and New Features

  • Localization support:
    Added translations for multiple languages (English, Czech). The extension UI now adapts to the selected language.
  • Send message command:
    Users can now send messages directly from Raycast using the new command.
  • Unread chats command:
    Added a new command to display all chats with unread messages.
  • Improved chat search:
    Chats are only displayed in search results when a keyword is entered, making search more relevant and efficient.
  • Enhanced chat listing:
    Listing of chats has been fixed and improved to ensure correct display and functionality.

Written with the assistance of AI. It will definitely need improvement and review :)


@coderabbitai
Copy link
Contributor

coderabbitai bot commented Nov 28, 2025

📝 Walkthrough

Summary by CodeRabbit

  • New Features

    • Five new commands: List Chats, Search Chats, Unread Chats, Send Message, and Search Messages
    • Language preference option with English and Czech support
    • Enhanced chat search with pagination for better performance
    • Improved chat display with avatars and network-specific icons
  • Documentation

    • Added "Available Commands" section detailing all commands with usage instructions
    • Added "NPM Commands" section documenting development scripts

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

Walkthrough

Replaces the single find-chat command with multiple focused commands, adds English/Czech i18n and a language preference, introduces icon generation and avatar/network utilities, shared components/hooks, and localizes HUD/UI messages.

Changes

Cohort / File(s) Summary
Documentation
README.md
Added "Available Commands" section and an NPM scripts table.
Package manifest
package.json
Removed find-chat; added list-chats, search-chats, unread-chats, send-message, search-messages; added language preference (en/cs); added devDependency simple-icons.
Icon generation script
scripts/generate-icons.js
New Node script that extracts SVGs from simple-icons and writes them to assets/icons.
Localization bundles & loader
src/locales/en.ts, src/locales/cs.ts, src/locales/index.ts
Added English and Czech translation files and a runtime t() loader with setLocale() and getCurrentLocale().
Core API & HUD/localization
src/api.ts
Switched from AppFocusParams/app.focus() to AppOpenParams/app.open(); localized HUD/toast messages; useBeeperDesktop now accepts dependency arrays.
Removed command
src/find-chat.tsx
Deleted the previous FindChatCommand and its default export.
New commands / UIs
src/list-chats.tsx, src/search-chats.tsx, src/unread-chats.tsx, src/send-message.tsx, src/search-messages.tsx
Added localized Raycast commands: list chats (paginated/searchable), search chats, unread chats (aggregated/paginated), send message form, and message search.
Updated command
src/list-accounts.tsx
Replaced hard-coded strings with translation usage via t().
Shared UI component
src/components/ChatListItem.tsx
New reusable ChatListItem component rendering icon/title/subtitle/accessories and actions (Open, Show Details, Copy ID).
Search hooks
src/hooks/useChatSearch.ts, src/hooks/useMessageSearch.ts
New hooks for chat and message search with async iteration, batching, deduplication, and enrichment.
Avatar & network utilities
src/utils/avatar.ts, src/utils/networkIcons.ts, src/utils/chatIcon.ts
New utilities: safeAvatarPath() (file:// validation and containment), getNetworkIcon() (network→asset mapping), and getChatIcon() (avatar or network icon fallback).

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45–75 minutes

  • Focus review on:
    • src/utils/avatar.ts — path normalization, containment logic, platform-specific allowed bases (security-sensitive).
    • src/hooks/useChatSearch.ts & src/hooks/useMessageSearch.ts — async iteration, batching limits, deduplication, and dependency behavior.
    • src/locales/index.ts — locale precedence, preference handling, and runtime switching.
    • src/api.ts — changed focusApp signature, useBeeperDesktop deps, and translated HUD/toast usage across call sites.
    • New UI components/commands — icon resolution, safe avatar handling, throttling/search UX, form validation, and action handlers.

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 78.26% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The pull request title directly summarizes the main changes: localization support, message sending capability, unread chats command, and improved chat search and listing functionality.
Description check ✅ Passed The description is well-organized and clearly relates to the changeset, covering all major features added: localization, send message command, unread chats command, improved chat search, and enhanced chat listing.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

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

❤️ Share

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

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

♻️ Duplicate comments (1)
src/search-chats.tsx (1)

7-27: Duplicate code with src/unread-chats.tsx.

This is the same getNetworkIcon function duplicated from src/unread-chats.tsx. See the comment on that file for the refactoring suggestion.

🧹 Nitpick comments (7)
scripts/generate-icons.js (1)

49-51: Simplify the filename handling.

The code creates a .png filename and immediately replaces it with .svg, which is unnecessarily convoluted.

-  const filename = `${name}.png`;
-  fs.writeFileSync(path.join(outputDir, filename.replace('.png', '.svg')), svg);
-  console.log(`Generated ${filename.replace('.png', '.svg')}`);
+  const filename = `${name}.svg`;
+  fs.writeFileSync(path.join(outputDir, filename), svg);
+  console.log(`Generated ${filename}`);
src/list-accounts.tsx (3)

26-26: Semantic mismatch: using "unnamedChat" for accounts.

The translation key unnamedChat is semantically incorrect for an unnamed account. Consider adding a dedicated unnamedAccount key or renaming the existing key to something more generic like unnamed.


33-44: Incomplete localization: metadata labels remain hardcoded.

These metadata labels ("Account ID", "Network", "User ID", etc.) are still hardcoded in English while other UI strings use translations. For consistency, consider adding these to the translations object.


61-73: Incomplete localization: action titles remain hardcoded.

Action titles like "Focus Beeper Desktop", "Toggle Details", and "Show Details (Old)" are hardcoded while other strings use translations. This creates inconsistency when the user switches languages.

src/unread-chats.tsx (1)

6-26: Extract getNetworkIcon to a shared utility.

This function is duplicated verbatim in src/search-chats.tsx (lines 7-27). Extract it to a shared module (e.g., src/utils.ts or src/icons.ts) to avoid maintaining the same code in multiple places.

Create a new file src/icons.ts:

import { Icon, Image } from "@raycast/api";

export function getNetworkIcon(network: string): Image.ImageLike {
  const networkLower = network.toLowerCase().replace(/[/\s-]/g, "");

  const iconMap: Record<string, string> = {
    slack: "slack.svg",
    whatsapp: "whatsapp.svg",
    telegram: "telegram.svg",
    discord: "discord.svg",
    instagram: "instagram.svg",
    facebook: "facebook.svg",
    facebookmessenger: "messenger.svg",
    messenger: "messenger.svg",
    signal: "signal.svg",
    imessage: "imessage.svg",
    twitter: "twitter.svg",
    email: "email.svg",
    googlemessages: "google-messages.svg",
  };

  return iconMap[networkLower] || Icon.Message;
}
src/locales/index.ts (1)

34-39: Consider logging or returning success for invalid locale attempts.

setLocale silently ignores unsupported locales. This could make debugging harder if a caller passes an invalid locale code. Consider returning a boolean or logging a warning.

 export function setLocale(locale: string): void {
   if (translations[locale]) {
     currentLocale = locale;
     currentTranslations = translations[locale];
+  } else {
+    console.warn(`Locale "${locale}" not supported, keeping current locale "${currentLocale}"`);
   }
 }
src/list-chats.tsx (1)

7-27: Consider extracting getNetworkIcon to a shared utility.

Based on the AI summary, similar network icon logic is used in search-chats.tsx and unread-chats.tsx. Extracting this to a shared utility (e.g., src/utils.ts) would reduce duplication and ensure consistency.

Example structure:

// src/utils.ts
import { Icon, Image } from "@raycast/api";

export function getNetworkIcon(network: string): Image.ImageLike {
  const networkLower = network.toLowerCase().replace(/[/\s-]/g, "");
  const iconMap: Record<string, string> = {
    slack: "slack.svg",
    // ... rest of mappings
  };
  return iconMap[networkLower] || Icon.Message;
}
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between cf04bc3 and 65c2f6b.

⛔ Files ignored due to path filters (13)
  • assets/discord.svg is excluded by !**/*.svg
  • assets/email.svg is excluded by !**/*.svg
  • assets/facebook.svg is excluded by !**/*.svg
  • assets/google-messages.svg is excluded by !**/*.svg
  • assets/imessage.svg is excluded by !**/*.svg
  • assets/instagram.svg is excluded by !**/*.svg
  • assets/messenger.svg is excluded by !**/*.svg
  • assets/signal.svg is excluded by !**/*.svg
  • assets/slack.svg is excluded by !**/*.svg
  • assets/telegram.svg is excluded by !**/*.svg
  • assets/twitter.svg is excluded by !**/*.svg
  • assets/whatsapp.svg is excluded by !**/*.svg
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (13)
  • README.md (1 hunks)
  • package.json (3 hunks)
  • scripts/generate-icons.js (1 hunks)
  • src/api.ts (2 hunks)
  • src/find-chat.tsx (0 hunks)
  • src/list-accounts.tsx (3 hunks)
  • src/list-chats.tsx (1 hunks)
  • src/locales/cs.ts (1 hunks)
  • src/locales/en.ts (1 hunks)
  • src/locales/index.ts (1 hunks)
  • src/search-chats.tsx (1 hunks)
  • src/send-message.tsx (1 hunks)
  • src/unread-chats.tsx (1 hunks)
💤 Files with no reviewable changes (1)
  • src/find-chat.tsx
🧰 Additional context used
🧬 Code graph analysis (9)
src/list-accounts.tsx (1)
src/locales/index.ts (1)
  • t (30-32)
src/locales/en.ts (1)
src/locales/index.ts (1)
  • Translations (45-45)
src/locales/cs.ts (2)
src/locales/en.ts (1)
  • Translations (84-84)
src/locales/index.ts (1)
  • Translations (45-45)
src/send-message.tsx (2)
src/locales/index.ts (1)
  • t (30-32)
src/api.ts (3)
  • useBeeperDesktop (67-71)
  • getBeeperDesktop (50-65)
  • createBeeperOAuth (31-48)
src/search-chats.tsx (2)
src/locales/index.ts (1)
  • t (30-32)
src/api.ts (2)
  • useBeeperDesktop (67-71)
  • focusApp (73-83)
src/list-chats.tsx (2)
src/locales/index.ts (1)
  • t (30-32)
src/api.ts (2)
  • useBeeperDesktop (67-71)
  • focusApp (73-83)
src/api.ts (1)
src/locales/index.ts (1)
  • t (30-32)
src/unread-chats.tsx (2)
src/locales/index.ts (1)
  • t (30-32)
src/api.ts (3)
  • useBeeperDesktop (67-71)
  • focusApp (73-83)
  • createBeeperOAuth (31-48)
src/locales/index.ts (1)
src/locales/en.ts (1)
  • Translations (84-84)
🔇 Additional comments (20)
README.md (1)

23-68: LGTM! Clear and well-structured documentation.

The command descriptions and NPM scripts table align with the package.json definitions and provide helpful guidance for users.

package.json (3)

22-45: LGTM! Well-structured command definitions.

The new commands are properly defined with appropriate modes and descriptions that align with the documented features.


56-67: LGTM! Language preference configuration looks correct.

The dropdown preference for language selection with English and Czech options is properly structured for Raycast.


80-80: simple-icons version is current and stable.

Version ^15.22.0 is the latest available version on npm (released November 23, 2025) with no deprecation warnings. The package is actively maintained with regular releases.

src/locales/en.ts (1)

1-84: LGTM! Well-structured localization foundation.

The translations object is logically organized with clear separation between commands and common strings. The use of functions for dynamic strings (e.g., unreadCount, totalCount) provides type safety while enabling parameterized translations.

src/unread-chats.tsx (1)

28-86: Well-structured component with proper data flow.

The implementation correctly:

  • Filters and sorts chats by unread count during fetch
  • Computes total unread count efficiently
  • Handles empty state appropriately
  • Uses proper accessories for visual indicators
src/api.ts (2)

67-71: Acceptable workaround for type overload issue.

The @ts-ignore is documented with a clear explanation. Consider opening an issue upstream or with @raycast/utils to get proper type support for optional dependency arrays.


73-82: Clean integration of localization with proper error handling.

The function correctly uses localized messages and maintains good error handling with console logging for debugging.

src/locales/cs.ts (1)

1-84: Well-structured localization file with proper type safety.

The file correctly imports the Translations type from ./en to ensure structural consistency. The Czech translations appear complete and follow the same structure as the English locale.

Consider having a native Czech speaker verify the translations for accuracy and natural phrasing.

src/locales/index.ts (1)

10-25: Robust locale resolution with sensible fallbacks.

The function handles multiple locale formats (e.g., en-US, en_US) and provides a safe fallback to English. The any cast is an acceptable workaround for the missing type on environment.locale.

src/search-chats.tsx (2)

53-92: Conditional rendering logic is correct.

The empty view handling works properly:

  • Empty search text shows the "start typing" prompt
  • Non-empty search with results shows the list
  • Non-empty search without results shows the "no results" view

The chats.map() on an empty array correctly produces no elements, allowing the no-results EmptyView to display.


32-44: Efficient search implementation with proper dependency tracking.

The hook correctly:

  • Depends on searchText to trigger re-fetches
  • Returns early with an empty array when there's no search term
  • Works well with the throttle prop on the List component
src/send-message.tsx (5)

1-5: LGTM!

Imports are well-organized and appropriate for the component's functionality.


7-10: LGTM!

Clean interface definition for form values.


15-26: LGTM!

The chat loading and sorting by recent activity is well-implemented.


59-95: LGTM!

Good use of storeValue on the dropdown for improved UX, and consistent translation usage throughout the form.


97-97: LGTM!

Export pattern is consistent with other commands in the extension.

src/list-chats.tsx (3)

1-6: LGTM!

Imports are appropriate for the component's functionality.


29-42: LGTM!

Search-driven chat loading with proper dependency tracking and throttling.


91-102: LGTM!

EmptyView condition and export pattern are correctly implemented.

Comment on lines 48 to 53
} catch (error) {
await showToast({
style: Toast.Style.Failure,
title: translations.commands.sendMessage.errorTitle,
message: error instanceof Error ? error.message : "Unknown error occurred",
});
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Use translated string for unknown error fallback.

The fallback error message is hardcoded in English, which breaks the localization pattern used throughout the file.

-        message: error instanceof Error ? error.message : "Unknown error occurred",
+        message: error instanceof Error ? error.message : translations.common.unknownError,

Ensure you add the unknownError key to your translation files if it doesn't already exist.

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

🤖 Prompt for AI Agents
In src/send-message.tsx around lines 48 to 53, the catch block uses a hardcoded
English string "Unknown error occurred" for the fallback message which breaks
localization; replace that hardcoded string with
translations.commands.sendMessage.unknownError (or the appropriate translations
path used in this file) so the fallback uses the translated string, and ensure
you add the unknownError key to all translation files with the proper localized
values.

@fery25
Copy link
Author

fery25 commented Nov 28, 2025

@coderabbitai generate docstrings

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Nov 28, 2025

✅ Actions performed

Initiated docstring generation; will generate only if new commits exist.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Nov 28, 2025

Note

Docstrings generation - SUCCESS
Generated docstrings for this pull request at #4

coderabbitai bot added a commit that referenced this pull request Nov 28, 2025
Docstrings generation was requested by @fery25.

* #3 (comment)

The following files were modified:

* `src/api.ts`
* `src/list-accounts.tsx`
* `src/list-chats.tsx`
* `src/locales/index.ts`
* `src/search-chats.tsx`
* `src/send-message.tsx`
* `src/unread-chats.tsx`
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)
src/list-chats.tsx (2)

85-95: Apply the same unnamed-chat fallback in the Detail markdown header.

The list item title already falls back to translations.common.unnamedChat, but the Detail header uses chat.title directly. For consistency and nicer UX when the title is missing, use the same fallback:

-                    markdown={`# ${chat.title}
+                    markdown={`# ${chat.title || translations.common.unnamedChat}

70-72: Fix accessories construction so empty objects aren’t passed through.

filter(Boolean) does not remove {} because empty objects are truthy, so you can still end up passing an empty accessory when lastActivity is missing.

Refactor to only append the date accessory when it exists:

-          accessories={[{ text: chat.type }, chat.lastActivity ? { date: new Date(chat.lastActivity) } : {}].filter(
-            Boolean,
-          )}
+          accessories={[
+            { text: chat.type },
+            ...(chat.lastActivity ? [{ date: new Date(chat.lastActivity) }] : []),
+          ]}
src/send-message.tsx (1)

57-62: Localize the unknown-error fallback in the failure toast.

The fallback string "Unknown error occurred" is hardcoded in English and breaks the otherwise consistent localization in this command. Prefer a translated key instead (e.g., translations.common.unknownError or a translations.commands.sendMessage.unknownError entry).

Example:

-        message: error instanceof Error ? error.message : "Unknown error occurred",
+        message: error instanceof Error ? error.message : translations.common.unknownError,

Make sure the chosen key exists in all locale files.

🧹 Nitpick comments (2)
src/search-chats.tsx (1)

7-33: Consider centralizing getNetworkIcon to avoid duplication.

This helper (normalization + icon map) appears identical to the one in src/list-chats.tsx. Pulling it into a shared module (e.g., ./network-icons) would reduce duplication and keep the icon mapping consistent across commands when you add/change networks.

src/api.ts (1)

72-83: Optional: tighten typing to avoid @ts-ignore on useBeeperDesktop.

The hook wrapper is fine functionally, but the @ts-ignore around usePromise is a small type-hygiene smell. If you want to clean this up later, you could add overloads for useBeeperDesktop (with and without deps) that mirror usePromise’s signature so you don’t need to suppress the type checker.

Not urgent, but worth considering when you next touch this file.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 65c2f6b and b507f31.

📒 Files selected for processing (7)
  • src/api.ts (3 hunks)
  • src/list-accounts.tsx (3 hunks)
  • src/list-chats.tsx (1 hunks)
  • src/locales/index.ts (1 hunks)
  • src/search-chats.tsx (1 hunks)
  • src/send-message.tsx (1 hunks)
  • src/unread-chats.tsx (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
  • src/list-accounts.tsx
  • src/locales/index.ts
  • src/unread-chats.tsx
🧰 Additional context used
🧬 Code graph analysis (3)
src/list-chats.tsx (2)
src/locales/index.ts (1)
  • t (42-44)
src/api.ts (3)
  • useBeeperDesktop (79-83)
  • focusApp (85-95)
  • createBeeperOAuth (31-48)
src/api.ts (1)
src/locales/index.ts (1)
  • t (42-44)
src/search-chats.tsx (2)
src/locales/index.ts (1)
  • t (42-44)
src/api.ts (3)
  • useBeeperDesktop (79-83)
  • focusApp (85-95)
  • createBeeperOAuth (31-48)
🔇 Additional comments (5)
src/search-chats.tsx (1)

35-107: Search behavior and localized UI look solid.

The command correctly:

  • Skips remote search when searchText is empty.
  • Streams results via client.chats.search({ query: searchText }).
  • Uses localized placeholders/empty states and a distinct “no results” view.
  • Shows good metadata (network, unread/pinned/muted) and focuses the chat via focusApp.

No issues from a correctness/UX standpoint.

src/api.ts (2)

50-70: BeeperDesktop client caching logic looks correct.

getBeeperDesktop cleanly reuses a single client instance and refreshes it when either baseURL or the access token changes. This matches expected behavior with the OAuth flow and should avoid stale connections.


85-95: Localized focusApp behavior and HUD feedback look good.

Switching to app.open(params) and wiring success/error HUD messages through t() provides a nice localized UX, while still closing the main window on success. The error logging remains in English, which is fine for internal logs.

src/send-message.tsx (1)

21-103: Send-message form behavior and UX look good overall.

  • Chats are fetched once via useBeeperDesktop and sorted by lastActivity (newest first).
  • Submission validates both chatId and message, with translated failure feedback.
  • On success, the command closes the main window and shows a localized HUD message.
  • Form fields and actions are fully localized and use appropriate Raycast components.

Aside from the error-fallback string, the implementation looks solid.

src/list-chats.tsx (1)

35-112: ListChatsCommand search, localization, and actions look well-structured.

The command:

  • Uses localized search placeholder and empty-state copy.
  • Streams chats from client.chats.search with search-text–driven params.
  • Shows network-specific icons, localized unnamed-chat fallback, and last-activity/date accessory.
  • Provides sensible actions (open in Beeper, show details, copy ID) with localized labels.

Functionally this all looks good.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (6)
src/locales/cs.ts (1)

61-64: Inconsistent indentation on unknownError.

Line 63 has extra leading spaces compared to the surrounding properties, which breaks the visual alignment.

   common: {
     unnamedChat: "Nepojmenovaný chat",
-      unknownError: "Došlo k neznámé chybě",
+    unknownError: "Došlo k neznámé chybě",
     openInBeeper: "Otevřít chat v Beeper",
src/network-icons.ts (1)

12-26: Consider hoisting iconMap outside the function.

The iconMap object is recreated on every call. Moving it to module scope avoids repeated allocations, though the impact is minimal for this use case.

+const NETWORK_ICON_MAP: Record<string, string> = {
+  slack: "slack.svg",
+  whatsapp: "whatsapp.svg",
+  telegram: "telegram.svg",
+  discord: "discord.svg",
+  instagram: "instagram.svg",
+  facebook: "facebook.svg",
+  facebookmessenger: "messenger.svg",
+  messenger: "messenger.svg",
+  signal: "signal.svg",
+  imessage: "imessage.svg",
+  twitter: "twitter.svg",
+  email: "email.svg",
+  googlemessages: "google-messages.svg",
+};
+
 export function getNetworkIcon(network: string): Image.ImageLike {
   const networkLower = network.toLowerCase().replace(/[/\s-]/g, "");
-
-  const iconMap: Record<string, string> = {
-    slack: "slack.svg",
-    whatsapp: "whatsapp.svg",
-    telegram: "telegram.svg",
-    discord: "discord.svg",
-    instagram: "instagram.svg",
-    facebook: "facebook.svg",
-    facebookmessenger: "messenger.svg",
-    messenger: "messenger.svg",
-    signal: "signal.svg",
-    imessage: "imessage.svg",
-    twitter: "twitter.svg",
-    email: "email.svg",
-    googlemessages: "google-messages.svg",
-  };
-
-  return iconMap[networkLower] || Icon.Message;
+  return NETWORK_ICON_MAP[networkLower] || Icon.Message;
 }
src/list-chats.tsx (2)

2-2: Remove unused Image import.

Image is imported but never used in this file.

-import { ActionPanel, Detail, List, Action, Icon, Image } from "@raycast/api";
+import { ActionPanel, Detail, List, Action, Icon } from "@raycast/api";

18-28: Streaming search collects all results before rendering.

The for await loop accumulates all chats into an array before returning. This means users won't see incremental results. For large result sets, consider a different pattern that updates state progressively, though this may be acceptable for typical chat counts.

src/search-chats.tsx (2)

1-1: Remove unused Image import.

Image is imported but never used in this file.

-import { ActionPanel, Action, List, Icon, Image } from "@raycast/api";
+import { ActionPanel, Action, List, Icon } from "@raycast/api";

41-80: Two EmptyView components may cause confusion.

The conditional rendering logic is correct, but having two separate EmptyView components (lines 42-46 for empty search, lines 74-80 for no results) makes the JSX harder to follow. Consider consolidating into a single conditional block for clarity.

       {searchText === "" ? (
         <List.EmptyView
           icon={Icon.MagnifyingGlass}
           title={translations.commands.searchChats.emptyTitle}
           description={translations.commands.searchChats.emptyDescription}
         />
+      ) : !isLoading && chats.length === 0 ? (
+        <List.EmptyView
+          icon={Icon.Message}
+          title={translations.commands.searchChats.noResultsTitle}
+          description={translations.commands.searchChats.noResultsDescription}
+        />
       ) : (
         chats.map((chat) => (
           // ... existing List.Item code
         ))
       )}
-      {searchText !== "" && !isLoading && chats.length === 0 && (
-        <List.EmptyView
-          icon={Icon.Message}
-          title={translations.commands.searchChats.noResultsTitle}
-          description={translations.commands.searchChats.noResultsDescription}
-        />
-      )}
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between b507f31 and 9714e2d.

📒 Files selected for processing (6)
  • src/list-chats.tsx (1 hunks)
  • src/locales/cs.ts (1 hunks)
  • src/locales/en.ts (1 hunks)
  • src/network-icons.ts (1 hunks)
  • src/search-chats.tsx (1 hunks)
  • src/send-message.tsx (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/send-message.tsx
  • src/locales/en.ts
🧰 Additional context used
🧬 Code graph analysis (3)
src/list-chats.tsx (3)
src/locales/index.ts (1)
  • t (42-44)
src/api.ts (3)
  • useBeeperDesktop (79-83)
  • focusApp (85-95)
  • createBeeperOAuth (31-48)
src/network-icons.ts (1)
  • getNetworkIcon (9-29)
src/locales/cs.ts (2)
src/locales/en.ts (1)
  • Translations (85-85)
src/locales/index.ts (1)
  • Translations (67-67)
src/search-chats.tsx (3)
src/locales/index.ts (1)
  • t (42-44)
src/api.ts (3)
  • useBeeperDesktop (79-83)
  • focusApp (85-95)
  • createBeeperOAuth (31-48)
src/network-icons.ts (1)
  • getNetworkIcon (9-29)
🔇 Additional comments (5)
src/locales/cs.ts (1)

1-85: Translation structure looks good.

The Czech translations correctly implement the Translations type and mirror the English structure. The dynamic count functions for unread and total counts are properly typed.

src/list-chats.tsx (2)

43-46: Previous review comment addressed: accessories array handling.

The spread pattern correctly handles the conditional lastActivity accessory, avoiding the previous issue with empty objects passing through filter(Boolean).


59-59: Previous review comment addressed: title fallback in Detail markdown.

The fallback to translations.common.unnamedChat is now correctly applied in the Detail markdown header.

src/search-chats.tsx (1)

17-85: Implementation looks solid overall.

The search command correctly handles empty state, loading state, and no-results state with proper localization. The use of focusApp for opening chats and the async iterator pattern for fetching results are consistent with the list-chats implementation.

src/network-icons.ts (1)

9-10: File under review has been deleted in the current branch.

The src/network-icons.ts file and all referenced SVG assets were removed in commit cf04bc3 (current HEAD). In the previous commit (9714e2d), all 13 SVG files (slack.svg, whatsapp.svg, telegram.svg, discord.svg, instagram.svg, facebook.svg, messenger.svg, signal.svg, imessage.svg, twitter.svg, email.svg, google-messages.svg) existed and were correctly referenced by the getNetworkIcon function. Since the entire file and assets have been deleted, this review comment is no longer applicable.

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 (2)
src/search-chats.tsx (2)

17-86: Consider extracting shared chat rendering logic.

There's substantial code duplication between search-chats.tsx and list-chats.tsx:

  • The useBeeperDesktop streaming pattern (lines 20-32 here vs 18-28 in list-chats)
  • The List.Item rendering logic (lines 59-82 here vs 37-77 in list-chats)
  • Action definitions for Open in Beeper and Copy Chat ID

This duplication increases maintenance burden and the risk of inconsistencies.

Consider creating shared utilities:

Option 1: Extract a ChatListItem component that accepts chat, translations, and optional accessories configuration:

// src/components/ChatListItem.tsx
export function ChatListItem({ 
  chat, 
  translations, 
  showDetails = true,
  accessories = []
}) {
  return (
    <List.Item
      key={chat.id}
      icon={getNetworkIcon(chat.network)}
      title={chat.title || translations.common.unnamedChat}
      subtitle={chat.network}
      accessories={accessories}
      actions={/* ... shared actions ... */}
    />
  );
}

Option 2: Extract the streaming search logic into a custom hook:

// src/hooks/useChatSearch.ts
export function useChatSearch(searchText: string, includeEmpty = false) {
  return useBeeperDesktop(
    async (client) => {
      if (!searchText && !includeEmpty) return [];
      const allChats = [];
      const params = searchText ? { query: searchText } : {};
      for await (const chat of client.chats.search(params)) {
        allChats.push(chat);
      }
      return allChats;
    },
    [searchText]
  );
}

This would reduce duplication and make both commands easier to maintain.


66-66: Borrowing translation from unreadChats command.

Line 66 uses translations.commands.unreadChats.unreadCount() within the searchChats command. While this works functionally, it creates coupling between command translations.

Consider either:

  1. Adding a shared unreadCount helper in common translations for reuse across commands
  2. Adding searchChats.unreadCount if this command should have independent translations

Apply this diff for option 1:

 common: {
   unnamedChat: "Unnamed chat",
+  unreadCount: (count: number) => `${count} unread`,
   // ... rest of common translations
 }

Then update the usage:

-  ? [{ text: translations.commands.unreadChats.unreadCount(chat.unreadCount) }]
+  ? [{ text: translations.common.unreadCount(chat.unreadCount) }]
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 9714e2d and e3071eb.

📒 Files selected for processing (5)
  • src/list-chats.tsx (1 hunks)
  • src/locales/cs.ts (1 hunks)
  • src/locales/en.ts (1 hunks)
  • src/network-icons.ts (1 hunks)
  • src/search-chats.tsx (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/network-icons.ts
  • src/locales/en.ts
🧰 Additional context used
🧬 Code graph analysis (3)
src/list-chats.tsx (3)
src/locales/index.ts (1)
  • t (42-44)
src/api.ts (2)
  • useBeeperDesktop (79-83)
  • focusApp (85-95)
src/network-icons.ts (1)
  • getNetworkIcon (25-28)
src/locales/cs.ts (2)
src/locales/en.ts (1)
  • Translations (85-85)
src/locales/index.ts (1)
  • Translations (67-67)
src/search-chats.tsx (3)
src/locales/index.ts (1)
  • t (42-44)
src/api.ts (3)
  • useBeeperDesktop (79-83)
  • focusApp (85-95)
  • createBeeperOAuth (31-48)
src/network-icons.ts (1)
  • getNetworkIcon (25-28)
🔇 Additional comments (3)
src/locales/cs.ts (1)

1-85: LGTM! Structure and typing are correct.

The Czech localization file is well-structured and correctly typed against the Translations interface. The dynamic translation functions (unreadCount, totalCount) have proper type signatures.

Note: I cannot verify the grammatical correctness or natural phrasing of the Czech translations. Consider having a native Czech speaker review the translation quality to ensure proper grammar, terminology, and user experience.

Would you like me to help identify a process for validating the translation quality, such as generating a checklist of strings to review or suggesting translation validation approaches?

src/list-chats.tsx (1)

1-89: LGTM! Well-implemented with localization and improved UX.

The refactoring successfully integrates:

  • Dynamic streaming search with proper state management
  • Comprehensive localization via the t() helper
  • Network-specific icons for better visual identification
  • Enhanced actions including detailed view with translated field labels
  • Proper conditional rendering for accessories and empty states

The past review comments regarding filter(Boolean) and title fallback have been correctly addressed. The implementation is clean and follows React best practices.

src/search-chats.tsx (1)

71-80: The review comment is based on incorrect information.

After examining both files, I found that search-chats.tsx does include the "Show Details" action (lines 26-45). The action displays a Detail component with markdown showing chat information, nearly identical to list-chats.tsx.

The actual difference is that search-chats.tsx has only the "Show Details" action, while list-chats.tsx includes additional actions like "Refresh" and "Focus Beeper Desktop" in its ActionPanel.

Additionally, the code snippet provided in the review (lines 71-80) does not correspond to search-chats.tsx, which is only 61 lines total. The snippet appears to be from list-chats.tsx instead.

Likely an incorrect or invalid review comment.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (3)
src/components/ChatListItem.tsx (2)

4-9: Consider adding proper types for props.

Using any for chat and translations reduces type safety and IDE assistance. Consider defining or importing proper types:

+import { Translations } from "../locales/en";
+
+interface Chat {
+  id: string;
+  network: string;
+  title?: string;
+  onOpen?: () => void;
+  detailsTarget?: React.ReactNode;
+}
+
 interface ChatListItemProps {
-  chat: any;
-  translations: any;
+  chat: Chat;
+  translations: Translations;
   accessories?: Array<{ text?: string; icon?: any; date?: Date }>;
   showDetails?: boolean;
 }

13-14: Remove redundant key prop.

The key prop on List.Item (line 14) is redundant here since the key should be provided by the parent component when mapping over ChatListItem components. React keys are only meaningful when used in an array context at the call site.

     <List.Item
-      key={chat.id}
       icon={getNetworkIcon(chat.network)}
src/search-chats.tsx (1)

1-1: Unused imports.

ActionPanel, Action, and Icon are imported but not used directly in this file since the rendering logic moved to ChatListItem. Consider removing unused imports.

-import { ActionPanel, Action, List, Icon} from "@raycast/api";
+import { List, Icon } from "@raycast/api";
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between e3071eb and c118e7f.

📒 Files selected for processing (5)
  • src/components/ChatListItem.tsx (1 hunks)
  • src/hooks/useChatSearch.ts (1 hunks)
  • src/locales/cs.ts (1 hunks)
  • src/locales/en.ts (1 hunks)
  • src/search-chats.tsx (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/locales/en.ts
🧰 Additional context used
🧬 Code graph analysis (4)
src/search-chats.tsx (4)
src/locales/index.ts (1)
  • t (42-44)
src/hooks/useChatSearch.ts (1)
  • useChatSearch (3-16)
src/components/ChatListItem.tsx (1)
  • ChatListItem (11-38)
src/api.ts (2)
  • focusApp (85-95)
  • createBeeperOAuth (31-48)
src/hooks/useChatSearch.ts (1)
src/api.ts (1)
  • useBeeperDesktop (79-83)
src/locales/cs.ts (2)
src/locales/en.ts (1)
  • Translations (86-86)
src/locales/index.ts (1)
  • Translations (67-67)
src/components/ChatListItem.tsx (1)
src/network-icons.ts (1)
  • getNetworkIcon (25-28)
🔇 Additional comments (1)
src/locales/cs.ts (1)

1-86: LGTM! Well-structured Czech locale file.

The file correctly imports the Translations type from ./en ensuring type safety and structural consistency with the English locale. The translation structure mirrors the English version with proper function signatures for dynamic strings (unreadCount, totalCount).

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 (6)
src/api.ts (1)

72-83: Avoid @ts-ignore in useBeeperDesktop and narrow on deps

You can keep the same runtime behavior and drop the @ts-ignore by branching on whether deps is provided, and also avoid relying on an implicit React namespace by importing the dependency list type:

-import type { AppOpenParams } from "@beeper/desktop-api/resources/app";
+import type { AppOpenParams } from "@beeper/desktop-api/resources/app";
+import type { DependencyList } from "react";
@@
-/**
- * Execute an asynchronous operation using the current BeeperDesktop client and return its managed result.
- *
- * @param fn - Function that receives the current BeeperDesktop client and returns a promise for the desired value
- * @param deps - Optional React dependency list that controls when the operation is re-run
- * @returns The value produced by `fn` when executed with the current BeeperDesktop client; loading and error state are managed by the hook
- */
-export function useBeeperDesktop<T>(fn: (client: BeeperDesktop) => Promise<T>, deps?: React.DependencyList) {
-  // eslint-disable-next-line @typescript-eslint/ban-ts-comment
-  // @ts-ignore - usePromise type overload issue with optional deps
-  return usePromise(async () => fn(getBeeperDesktop()), deps);
-}
+/**
+ * Execute an asynchronous operation using the current BeeperDesktop client and return its managed result.
+ */
+export function useBeeperDesktop<T>(fn: (client: BeeperDesktop) => Promise<T>, deps?: DependencyList) {
+  const factory = () => fn(getBeeperDesktop());
+  return deps ? usePromise(factory, deps) : usePromise(factory);
+}

This keeps the hook API the same while staying within the usePromise overloads and avoiding suppressed type errors.

src/utils/avatar.ts (1)

8-93: Avatar path validation is solid; consider a couple of hardening tweaks

The containment logic (normalize(resolve(...)) + isPathAllowed + null‑byte check) is strong. Two optional refinements you might consider:

  • Explicitly reject non‑file:// URLs up front (e.g., if (!url.startsWith("file://")) return undefined;) so future changes to parsing can’t accidentally start accepting other schemes.
  • The normalizedPath.includes("..") check may block perfectly valid filenames containing ..; if you trust normalize(resolve(...)) + isPathAllowed, you could drop this guard or tighten it to check path segments explicitly.

If symlink escapes under the allowed base are a concern, you could also resolve with fs.realpath before the containment check, at the cost of I/O.

src/components/ChatListItem.tsx (2)

5-25: De-duplicate getNetworkIcon mapping across modules

This icon map is effectively identical to the ones in src/list-chats.tsx and src/unread-chats.tsx. Centralizing it (e.g., in a small utils/networkIcons.ts helper) and reusing it across ChatListItem and the list commands would reduce drift when adding or renaming network icons.


27-42: Tighten Chat props typing and avoid implicit React global

detailsTarget?: React.ReactNode assumes a global React type namespace, and the accessories shape is manually duplicated from Raycast types. For better type safety and IDE support:

-import { List, ActionPanel, Action, Icon, Image } from "@raycast/api";
-import { Translations } from "../locales/en";
-import { safeAvatarPath } from "../utils/avatar";
+import { List, ActionPanel, Action, Icon, Image } from "@raycast/api";
+import type { ReactNode } from "react";
+import { Translations } from "../locales/en";
+import { safeAvatarPath } from "../utils/avatar";
@@
 interface Chat {
@@
-  detailsTarget?: React.ReactNode;
+  detailsTarget?: ReactNode;
 }
 
 interface ChatListItemProps {
   chat: Chat;
   translations: Translations;
-  // eslint-disable-next-line @typescript-eslint/no-explicit-any
-  accessories?: Array<{ text?: string; icon?: any; date?: Date }>;
+  accessories?: List.Item.Props["accessories"];
   showDetails?: boolean;
 }

This removes reliance on a global React namespace and keeps accessories aligned with Raycast’s List.Item API.

src/search-chats.tsx (1)

14-23: Avoid running safeAvatarPath twice for the same avatar

getAvatarUrl already returns a sanitized filesystem path via safeAvatarPath, and ChatListItem will call safeAvatarPath again on avatarUrl. It’s harmless but redundant; you could either:

  • Have getAvatarUrl return the raw imgURL and let ChatListItem handle sanitization, or
  • Change ChatListItem to assume avatarUrl is already sanitized and skip re-validating it.

Either way, it keeps the responsibility for sanitization in one clear place.

Also applies to: 61-67

src/unread-chats.tsx (1)

57-115: Optionally surface errors from useBeeperDesktop instead of just “no unread”

The unread filtering, sorting, and accessories all look correct. One thing to consider: if useBeeperDesktop fails (auth/API error), the component will currently just show the “no unread chats” empty view once isLoading is false. Exposing error from the hook and either showing an error EmptyView or a toast would make failures distinguishable from a genuinely empty unread state.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between c118e7f and 30584b4.

📒 Files selected for processing (10)
  • src/api.ts (3 hunks)
  • src/components/ChatListItem.tsx (1 hunks)
  • src/hooks/useChatSearch.ts (1 hunks)
  • src/list-accounts.tsx (3 hunks)
  • src/list-chats.tsx (1 hunks)
  • src/locales/index.ts (1 hunks)
  • src/search-chats.tsx (1 hunks)
  • src/send-message.tsx (1 hunks)
  • src/unread-chats.tsx (1 hunks)
  • src/utils/avatar.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (4)
  • src/list-accounts.tsx
  • src/hooks/useChatSearch.ts
  • src/send-message.tsx
  • src/locales/index.ts
🧰 Additional context used
🧬 Code graph analysis (3)
src/api.ts (1)
src/locales/index.ts (1)
  • t (42-44)
src/components/ChatListItem.tsx (2)
src/locales/en.ts (1)
  • Translations (86-86)
src/utils/avatar.ts (1)
  • safeAvatarPath (62-93)
src/search-chats.tsx (5)
src/utils/avatar.ts (1)
  • safeAvatarPath (62-93)
src/locales/index.ts (1)
  • t (42-44)
src/hooks/useChatSearch.ts (1)
  • useChatSearch (3-16)
src/components/ChatListItem.tsx (1)
  • ChatListItem (54-73)
src/api.ts (1)
  • focusApp (85-95)
🔇 Additional comments (4)
src/api.ts (2)

50-70: Caching client and docstring match behavior

The JSDoc accurately reflects the caching behavior keyed off base URL and access token, and the instance reuse logic looks correct and safe.


85-95: Localized HUD messages in focusApp

Using t() for the success/error HUD messages and switching to app.open with default AppOpenParams = {} is a clean way to localize user feedback without changing behavior.

src/search-chats.tsx (1)

41-77: Search flow and localized empty states look good

Using useChatSearch(searchText) with a separate “type to search” empty view for searchText === "" and a localized “no results” view when the search returns nothing is clean. Mapping to ChatListItem with key={chat.id} and localized accessories (unread count, pin, mute) looks correct.

src/list-chats.tsx (1)

56-124: Searchable chat listing and localized Detail view look consistent

The search integration via useBeeperDesktop with [searchText] deps, localized search placeholder, unnamed title fallback, and the translated Detail markdown (id/account/network/type/unread/pin/mute/archive/lastActivity with yes/no/na) all look coherent. The accessories construction for chat.type and optional lastActivity avoids the earlier filter(Boolean) pitfall.

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 (3)
src/search-chats.tsx (1)

9-22: Consider adding proper TypeScript types.

The function correctly extracts avatar URLs for 1:1 chats and appropriately delegates sanitization to ChatListItem. However, using any types reduces type safety. If the chat and participant types are available from @beeper/desktop-api, consider using them.

-// eslint-disable-next-line @typescript-eslint/no-explicit-any
-function getAvatarUrl(chat: any): string | undefined {
+import type { Chat } from "@beeper/desktop-api/resources/chats";
+
+function getAvatarUrl(chat: Chat): string | undefined {
   // Only show avatar for 1:1 chats, not groups
   if (chat.type !== "group" && chat.participants?.items) {
-    // eslint-disable-next-line @typescript-eslint/no-explicit-any
-    const otherParticipant = chat.participants.items.find((p: any) => !p.isSelf);
+    const otherParticipant = chat.participants.items.find((p) => !p.isSelf);
     return otherParticipant?.imgURL;
   }
   return undefined;
 }
src/list-chats.tsx (2)

9-26: Consider adding proper TypeScript types.

The icon resolution logic correctly handles 1:1 chats (with validated avatars) and groups (with network icons). The security validation via safeAvatarPath is excellent. However, using any types reduces type safety—consider importing proper types from @beeper/desktop-api if available.

-// eslint-disable-next-line @typescript-eslint/no-explicit-any
-function getChatIcon(chat: any): Image.ImageLike {
+import type { Chat } from "@beeper/desktop-api/resources/chats";
+
+function getChatIcon(chat: Chat): Image.ImageLike {
   // For 1:1 chats, try to get the other person's avatar
   if (chat.type !== "group" && chat.participants?.items) {
-    // eslint-disable-next-line @typescript-eslint/no-explicit-any
-    const otherParticipant = chat.participants.items.find((p: any) => !p.isSelf);
+    const otherParticipant = chat.participants.items.find((p) => !p.isSelf);
     if (otherParticipant?.imgURL) {
       const validatedPath = safeAvatarPath(otherParticipant.imgURL);
       if (validatedPath) {
         return { source: validatedPath, mask: Image.Mask.Circle };
       }
     }
   }
   return getNetworkIcon(chat.network);
 }

38-48: Consider using the useChatSearch hook to reduce duplication.

The chat fetching logic (lines 40-45) duplicates the implementation in src/hooks/useChatSearch.ts. Both iterate through client.chats.search and build an array. Using the shared hook would improve maintainability and consistency across commands.

-  const { data: chats = [], isLoading } = useBeeperDesktop(
-    async (client) => {
-      const allChats = [];
-      const searchParams = searchText ? { query: searchText } : {};
-      for await (const chat of client.chats.search(searchParams)) {
-        allChats.push(chat);
-      }
-      return allChats;
-    },
-    [searchText],
-  );
+  const { data: chats = [], isLoading } = useChatSearch(searchText, true);

Note: The second parameter true enables includeEmpty to show all chats when searchText is empty.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 30584b4 and cecc5a0.

📒 Files selected for processing (9)
  • src/api.ts (3 hunks)
  • src/components/ChatListItem.tsx (1 hunks)
  • src/list-chats.tsx (1 hunks)
  • src/locales/cs.ts (1 hunks)
  • src/locales/en.ts (1 hunks)
  • src/search-chats.tsx (1 hunks)
  • src/unread-chats.tsx (1 hunks)
  • src/utils/avatar.ts (1 hunks)
  • src/utils/networkIcons.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (4)
  • src/components/ChatListItem.tsx
  • src/unread-chats.tsx
  • src/locales/en.ts
  • src/locales/cs.ts
🧰 Additional context used
🧬 Code graph analysis (2)
src/list-chats.tsx (4)
src/utils/avatar.ts (1)
  • safeAvatarPath (62-98)
src/utils/networkIcons.ts (1)
  • getNetworkIcon (30-40)
src/locales/index.ts (1)
  • t (42-44)
src/api.ts (2)
  • useBeeperDesktop (75-77)
  • focusApp (79-89)
src/search-chats.tsx (4)
src/locales/index.ts (1)
  • t (42-44)
src/hooks/useChatSearch.ts (1)
  • useChatSearch (3-16)
src/components/ChatListItem.tsx (1)
  • ChatListItem (33-52)
src/api.ts (1)
  • focusApp (79-89)
🔇 Additional comments (14)
src/api.ts (4)

2-2: LGTM!

The import updates correctly reflect the API changes (AppFocusParams → AppOpenParams) and introduce localization support.

Also applies to: 5-5


50-54: LGTM!

The docstring clearly documents the caching behavior and function purpose.


75-76: > Likely an incorrect or invalid review comment.


79-88: The code snippet in this review comment does not match the current codebase.

The review describes a focusApp function using app.open() with translated HUD messages via t(), but the actual code uses app.focus() with hardcoded HUD messages. There are no translation imports or locale files in the repository. The function shown in the review snippet does not exist in src/api.ts.

Verify the code review is based on the correct version of the file before proceeding.

Likely an incorrect or invalid review comment.

src/utils/networkIcons.ts (1)

30-40: LGTM!

The function correctly normalizes network names, looks up icons, and provides a sensible fallback. The normalization pattern replace(/[/\s-]/g, "") handles common variations effectively.

src/utils/avatar.ts (3)

8-34: LGTM!

The platform-specific base directory handling is correct and follows OS conventions. The Linux implementation properly respects XDG_DATA_HOME while providing a standard fallback.


43-49: LGTM!

The containment check correctly prevents prefix attacks by requiring either an exact match or a path separator after the base directory.


62-98: Excellent security implementation!

The safeAvatarPath function implements comprehensive security measures:

  • Explicit file:// scheme validation
  • Safe URI decoding with error handling
  • Null byte rejection
  • Path normalization and resolution
  • Strict containment validation
  • Graceful error handling

This prevents path traversal, prefix attacks, and other file system exploits.

src/search-chats.tsx (3)

1-7: LGTM!

The imports correctly reflect the refactored architecture using useChatSearch, ChatListItem, and localization.


24-32: LGTM!

The docstring provides a clear, comprehensive description of the component's behavior and UI states.


33-78: The review comment references code that does not exist in the repository.

The code snippet and concerns in the review comment do not match the actual implementation in src/search-chats.tsx. The review discusses:

  • const translations = t() and translations.common.unreadCount() function calls
  • useChatSearch() hook
  • ChatListItem component
  • Complex accessor logic with unread count, pinned, and muted icons

However, the actual file (61 lines total) uses:

  • useBeeperDesktop() hook
  • Direct List.Item components
  • Hardcoded strings ("Search chats...", "No chats found")
  • Simple accessory configuration with chat type and last activity

The review comment appears to be reviewing code from a different version, branch, or file entirely. Lines 33-78 as described in the review do not exist in the current repository state.

Likely an incorrect or invalid review comment.

src/list-chats.tsx (3)

1-7: LGTM!

The imports are well-organized and support the enhanced functionality (localization, avatar handling, network icons).


28-34: LGTM!

The docstring clearly describes the component's functionality and behavior.


50-104: LGTM!

The list rendering correctly integrates localization, handles empty states, and provides comprehensive actions. Previous review comments have been addressed:

  1. Line 63 uses the spread operator pattern for conditional accessories instead of filter(Boolean).
  2. Line 76 applies the translated fallback for unnamed chats in the Detail markdown header.

The component provides a good user experience with translated placeholders, empty states, and detailed chat information.

Comment on lines +7 to +21
const NETWORK_ICON_MAP: Record<string, string> = {
slack: "slack.svg",
whatsapp: "whatsapp.svg",
telegram: "telegram.svg",
discord: "discord.svg",
instagram: "instagram.svg",
facebook: "facebook.svg",
facebookmessenger: "messenger.svg",
messenger: "messenger.svg",
signal: "signal.svg",
imessage: "imessage.svg",
twitter: "twitter.svg",
email: "email.svg",
googlemessages: "google-messages.svg",
};
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

find . -type d -name "assets" | head -20

Repository: beeper/raycast

Length of output: 66


🏁 Script executed:

git ls-files | grep -E "\.(svg|assets)" | head -30

Repository: beeper/raycast

Length of output: 40


🏁 Script executed:

ls -la ./assets/

Repository: beeper/raycast

Length of output: 244


🏁 Script executed:

# Check for each SVG file referenced in NETWORK_ICON_MAP
for icon in slack.svg whatsapp.svg telegram.svg discord.svg instagram.svg facebook.svg messenger.svg signal.svg imessage.svg twitter.svg email.svg google-messages.svg; do
  if [ -f "./assets/$icon" ]; then
    echo "$icon"
  else
    echo "$icon"
  fi
done

Repository: beeper/raycast

Length of output: 236


All referenced SVG files are missing from the assets directory.

The NETWORK_ICON_MAP references 12 SVG files (slack.svg, whatsapp.svg, telegram.svg, discord.svg, instagram.svg, facebook.svg, messenger.svg, signal.svg, imessage.svg, twitter.svg, email.svg, google-messages.svg), but none of these files exist in ./assets/. The assets directory currently contains only extension-icon.png. Either these SVG files need to be added to the repository, or the icon mapping entries should be removed until the corresponding assets are available.

🤖 Prompt for AI Agents
In src/utils/networkIcons.ts around lines 7 to 21, the NETWORK_ICON_MAP
references 12 SVG filenames that are not present in ./assets/, causing broken
asset imports at runtime; fix this by either (1) adding the corresponding SVG
files to the ./assets/ directory with the exact filenames used in the map
(slack.svg, whatsapp.svg, telegram.svg, discord.svg, instagram.svg,
facebook.svg, messenger.svg, signal.svg, imessage.svg, twitter.svg, email.svg,
google-messages.svg), or (2) updating the map to only reference existing asset
files or a single generic fallback icon (e.g., extension-icon.png) and remove
any entries that cannot be supplied, and ensure any build/bundler import paths
match the assets location.

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

♻️ Duplicate comments (1)
src/list-chats.tsx (1)

9-26: Duplicate: Extract getChatIcon to a shared utility.

This is another instance of the duplicated getChatIcon function. See the earlier comment on src/unread-chats.tsx for the suggested refactoring to a shared utility module.

🧹 Nitpick comments (2)
src/hooks/useMessageSearch.ts (1)

3-46: LGTM with a minor naming suggestion.

The hook implementation is solid:

  • Early return for empty queries prevents unnecessary API calls
  • 50-message limit and batch size of 5 provide reasonable performance bounds
  • Per-chat error handling with console.warn allows partial results rather than failing entirely
  • Message augmentation with chat data is correctly implemented with a null fallback

Consider renaming the parameter from query to searchText for consistency with the function signature:

  return useBeeperDesktop(
-   async (client, query) => {
-     if (!query) return [];
+   async (client, searchText) => {
+     if (!searchText) return [];
      const allMessages = [];
      // Search for messages matching the query
      // We limit to a reasonable number or let the iterator handle it,
      // but for a simple list view, fetching the first batch is usually enough.
      // The API returns a PagePromise, so we can iterate.
-     for await (const message of client.messages.search({ query })) {
+     for await (const message of client.messages.search({ query: searchText })) {
        allMessages.push(message);
        if (allMessages.length >= 50) break;
      }
src/unread-chats.tsx (1)

36-52: LGTM with an optional performance suggestion.

The filtering and sorting logic is correct:

  • Filters chats with unreadCount > 0
  • Sorts by unread count in descending order (highest first)

For users with many chats, consider adding a reasonable limit to prevent performance issues:

  const allChats = [];
  for await (const chat of client.chats.search({})) {
    // Filter only chats with unread messages
    if (chat.unreadCount > 0) {
      allChats.push(chat);
+     // Limit to prevent performance issues with very large chat lists
+     if (allChats.length >= 100) break;
    }
  }
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between cecc5a0 and a604d5d.

📒 Files selected for processing (11)
  • package.json (4 hunks)
  • src/api.ts (3 hunks)
  • src/hooks/useChatSearch.ts (1 hunks)
  • src/hooks/useMessageSearch.ts (1 hunks)
  • src/list-accounts.tsx (3 hunks)
  • src/list-chats.tsx (1 hunks)
  • src/locales/cs.ts (1 hunks)
  • src/locales/en.ts (1 hunks)
  • src/search-messages.tsx (1 hunks)
  • src/send-message.tsx (1 hunks)
  • src/unread-chats.tsx (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (5)
  • src/send-message.tsx
  • src/list-accounts.tsx
  • src/locales/cs.ts
  • src/locales/en.ts
  • src/hooks/useChatSearch.ts
🧰 Additional context used
🧬 Code graph analysis (3)
src/hooks/useMessageSearch.ts (1)
src/api.ts (1)
  • useBeeperDesktop (79-84)
src/api.ts (1)
src/locales/index.ts (1)
  • t (42-44)
src/unread-chats.tsx (4)
src/utils/avatar.ts (1)
  • safeAvatarPath (62-98)
src/utils/networkIcons.ts (1)
  • getNetworkIcon (30-40)
src/locales/index.ts (1)
  • t (42-44)
src/api.ts (3)
  • useBeeperDesktop (79-84)
  • focusApp (86-96)
  • createBeeperOAuth (31-48)
🔇 Additional comments (13)
src/hooks/useMessageSearch.ts (1)

1-2: LGTM!

Imports are clean and minimal.

src/api.ts (4)

50-70: LGTM!

The docblock accurately describes the caching behavior. The implementation correctly invalidates and recreates the client when the base URL or access token changes.


72-84: LGTM!

The generic enhancement is well-designed:

  • Maintains backward compatibility with the previous signature
  • Type-safe with proper generic constraints
  • The docblock clearly explains the new dependency array behavior

86-96: Do not use AppOpenParams or change the method to app.open().

The import statement specifies AppFocusParams from @beeper/desktop-api/resources/app, and AppOpenParams does not exist in the codebase. The current implementation correctly uses app.focus(params) with AppFocusParams. Changing to app.open(params) with AppOpenParams will cause a type error and break the function.

If the intent is to open the app instead of focusing it, first verify that app.open() is available in the @beeper/desktop-api and update the import accordingly. The localization additions are appropriate; integrate them with the correct method and parameter type.

Likely an incorrect or invalid review comment.


2-2: No changes needed. Line 2 currently imports AppFocusParams (not AppOpenParams), and the code uses the compatible app.focus() method with this parameter type. The reference to line 89 and app.open() does not match the current codebase (file is 79 lines total). If reviewing an actual change to AppOpenParams, verify the corresponding API method calls are updated to use app.open() instead of app.focus().

package.json (2)

23-51: LGTM!

The new command structure is well-organized:

  • Five focused commands replace the single find-chat command
  • Consistent naming and structure
  • Clear titles and descriptions
  • Appropriate use of mode: "view" for all commands

62-79: LGTM!

The language preference is well-structured:

  • Dropdown with clear English and Czech options
  • Proper Unicode handling for "Čeština"
  • Sensible default to English
  • Correctly marked as optional
src/unread-chats.tsx (1)

54-104: LGTM!

The UI implementation is solid:

  • Correct calculation of totalUnread for the navigation title
  • Proper conditional rendering of empty states (error vs. no unread chats)
  • Correct use of spread operator for conditional accessories (avoiding the empty object issue)
  • Appropriate actions for opening chats and copying IDs
src/search-messages.tsx (2)

31-55: LGTM!

The search UX is well-implemented:

  • Throttled search prevents excessive API calls
  • Clear empty state before search ("Enter a search query...")
  • Distinct "no results" state after unsuccessful search
  • Proper use of the useMessageSearch hook

56-79: LGTM with proper null handling!

The message item rendering is well-implemented:

  • Proper optional chaining for chat?.title
  • Appropriate fallbacks for missing data (unknownMessage, unnamedChat)
  • Correct timestamp formatting
  • messageSortKey converted to String for API compatibility

The action on line 72 passes messageSortKey to focusApp. This should be verified as part of the AppOpenParams compatibility check flagged in the api.ts review.

src/list-chats.tsx (3)

28-48: LGTM!

The search-driven data fetching is well-implemented:

  • Local state for search text with proper dependency tracking
  • Conditional search parameters (all chats when query is empty, filtered when provided)
  • Clear docblock explaining the component's behavior

50-63: LGTM!

The List component is properly configured with throttled search and localized placeholders. The accessories array (line 63) correctly uses the spread operator pattern to conditionally include the lastActivity date, avoiding the empty object issue mentioned in past reviews.


64-103: LGTM!

The actions and Detail view are well-implemented:

  • Properly structured ActionPanel with three clear actions
  • Detail markdown includes the title fallback (line 76) as suggested in past reviews
  • All detail fields use translated labels with appropriate formatting
  • Boolean values correctly show yes/no translations
  • Empty view is properly conditioned and localized

All issues mentioned in past review comments have been addressed.

"@types/react": "19.0.10",
"eslint": "^9.22.0",
"prettier": "^3.5.3",
"simple-icons": "^15.22.0",
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🌐 Web query:

simple-icons npm latest version 2025

💡 Result:

As of December 4, 2025 the latest simple-icons npm version is 9.16.1. [1][2]

Sources:
[1] npm package page — simple-icons. [2] npm.io package summary for simple-icons.


🌐 Web query:

simple-icons npm security vulnerabilities CVE

💡 Result:

Short answer: There are no public CVEs for the simple-icons npm package itself. The only CVE I found referencing "Simple Icons" is for the WordPress plugin "Popular Brand Icons – Simple Icons" (CVE-2021-24503, stored XSS); that CVE affects the WordPress plugin (versions < 2.7.8), not the npm simple-icons library. [1][2]

Sources:

  • NVD entry for CVE-2021-24503 (Popular Brand Icons – Simple Icons WordPress plugin). [1]
  • Rapid7 / other vulnerability DB summary for the same CVE and package distinctions. [2]

If you want, I can run a package-specific scan (OSS advisory feeds: GitHub Advisory, Snyk, npm audit) against a particular simple-icons version you use.


Correct the simple-icons version to an available release.

The specified version ^15.22.0 does not exist. The latest available version of simple-icons is 9.16.1. The version constraint will cause installation to fail. Update to a valid version (e.g., ^9.16.0 or ^9.0.0). No security vulnerabilities exist for the package itself.

🤖 Prompt for AI Agents
In package.json around line 92, the dependency "simple-icons": "^15.22.0"
references a non-existent release and will break installs; change that version
to a valid release such as "^9.16.0" (or "^9.0.0"), save the file, then run your
package manager (npm install or yarn) to update node_modules and the lockfile
(package-lock.json or yarn.lock) so the correct version is recorded.

Comment on lines 8 to 25
/**
* Returns chat icon - contact avatar for DMs, network icon for groups.
*/
// eslint-disable-next-line @typescript-eslint/no-explicit-any
function getChatIcon(chat: any): Image.ImageLike {
// For 1:1 chats, try to get the other person's avatar
if (chat.type !== "group" && chat.participants?.items) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const otherParticipant = chat.participants.items.find((p: any) => !p.isSelf);
if (otherParticipant?.imgURL) {
const validatedPath = safeAvatarPath(otherParticipant.imgURL);
if (validatedPath) {
return { source: validatedPath, mask: Image.Mask.Circle };
}
}
}
return getNetworkIcon(chat.network);
}
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 | 🟠 Major

Extract getChatIcon to a shared utility module.

This function is duplicated across at least three files (src/unread-chats.tsx, src/search-messages.tsx, and src/list-chats.tsx). Extract it to a shared utility (e.g., src/utils/chatIcon.ts) to eliminate duplication and ensure consistency.

Create a new file src/utils/chatIcon.ts:

import { Image, Icon } from "@raycast/api";
import { safeAvatarPath } from "./avatar";
import { getNetworkIcon } from "./networkIcons";

/**
 * Returns chat icon - contact avatar for DMs, network icon for groups.
 */
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export function getChatIcon(chat: any): Image.ImageLike {
  // For 1:1 chats, try to get the other person's avatar
  if (chat.type !== "group" && chat.participants?.items) {
    // eslint-disable-next-line @typescript-eslint/no-explicit-any
    const otherParticipant = chat.participants.items.find((p: any) => !p.isSelf);
    if (otherParticipant?.imgURL) {
      const validatedPath = safeAvatarPath(otherParticipant.imgURL);
      if (validatedPath) {
        return { source: validatedPath, mask: Image.Mask.Circle };
      }
    }
  }
  return getNetworkIcon(chat.network);
}

Then import and use it in all three files:

+import { getChatIcon } from "./utils/chatIcon";
-
-/**
- * Returns chat icon - contact avatar for DMs, network icon for groups.
- */
-// eslint-disable-next-line @typescript-eslint/no-explicit-any
-function getChatIcon(chat: any): Image.ImageLike {
-  // ...implementation...
-}
🤖 Prompt for AI Agents
In src/unread-chats.tsx around lines 8 to 25, the getChatIcon function is
duplicated across multiple files; extract it into a shared utility module
src/utils/chatIcon.ts, moving the implementation there and exporting it; create
the new file importing required symbols (Image from @raycast/api, safeAvatarPath
from ./avatar, getNetworkIcon from ./networkIcons) and export getChatIcon
exactly as in the snippet, then update src/unread-chats.tsx,
src/search-messages.tsx, and src/list-chats.tsx to remove the local definition
and import { getChatIcon } from "src/utils/chatIcon" (or relative path as
appropriate), ensure ESLint directives are preserved or removed as necessary and
run type checks to fix any import path or type issues.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (1)
src/list-chats.tsx (1)

18-28: Consider adding pagination or a result limit for performance.

The current implementation fetches all chats when searchText is empty or provided (lines 21-24). For users with many chats, this could result in slow load times and high memory usage.

Consider adding a limit to the iteration:

     const { data: chats = [], isLoading } = useBeeperDesktop(
       async (client, query) => {
         const allChats = [];
         const searchParams = query ? { query } : {};
+        const MAX_CHATS = 200;
         for await (const chat of client.chats.search(searchParams)) {
           allChats.push(chat);
+          if (allChats.length >= MAX_CHATS) break;
         }
         return allChats;
       },
       [searchText],
     );
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between a604d5d and 6f8b2ef.

📒 Files selected for processing (5)
  • src/components/ChatListItem.tsx (1 hunks)
  • src/list-chats.tsx (1 hunks)
  • src/search-messages.tsx (1 hunks)
  • src/unread-chats.tsx (1 hunks)
  • src/utils/chatIcon.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/unread-chats.tsx
🧰 Additional context used
🧬 Code graph analysis (4)
src/utils/chatIcon.ts (2)
src/utils/avatar.ts (1)
  • safeAvatarPath (62-98)
src/utils/networkIcons.ts (1)
  • getNetworkIcon (30-40)
src/list-chats.tsx (3)
src/locales/index.ts (1)
  • t (42-44)
src/api.ts (2)
  • useBeeperDesktop (79-84)
  • focusApp (86-96)
src/utils/chatIcon.ts (1)
  • getChatIcon (9-24)
src/search-messages.tsx (4)
src/locales/index.ts (1)
  • t (42-44)
src/hooks/useMessageSearch.ts (1)
  • useMessageSearch (3-46)
src/utils/chatIcon.ts (1)
  • getChatIcon (9-24)
src/api.ts (2)
  • focusApp (86-96)
  • createBeeperOAuth (31-48)
src/components/ChatListItem.tsx (2)
src/locales/en.ts (1)
  • Translations (99-99)
src/utils/chatIcon.ts (1)
  • getChatIcon (9-24)
🔇 Additional comments (7)
src/utils/chatIcon.ts (1)

9-24: LGTM!

The function correctly handles null chats, validates avatar paths with safeAvatarPath, and provides appropriate fallbacks (Icon.Bubble for null, network icon for groups or when avatar is unavailable). The defensive checks and circular mask for avatars align well with the UI requirements.

src/components/ChatListItem.tsx (1)

22-41: Component structure is well-designed.

The component properly uses optional chaining for chat.onOpen?.(), provides appropriate fallbacks for missing titles, and conditionally renders the Show Details action. The action panel structure follows Raycast best practices.

Note: Address the Chat interface type mismatch flagged in the previous comment to ensure getChatIcon works correctly.

src/list-chats.tsx (2)

8-14: LGTM!

The docstring clearly describes the component's functionality and aligns with the implementation below.


37-82: LGTM!

The list rendering correctly handles:

  • Chat icons via getChatIcon with network/avatar fallbacks
  • Title fallbacks using translations
  • Conditional accessories using the spread operator (previous filter(Boolean) issue resolved)
  • Localized markdown in the Details view (previous title fallback issue resolved)
  • Proper empty state with translated messages
src/search-messages.tsx (3)

9-12: LGTM!

The state management and hook usage follow React best practices. The useMessageSearch hook is properly invoked with searchText as a dependency.


21-33: LGTM!

The dual empty states (initial prompt and no results) provide clear user guidance and follow Raycast UX patterns effectively.


34-56: Message list rendering is well-structured.

The list items correctly:

  • Use the shared getChatIcon utility (previous duplication concern resolved)
  • Provide comprehensive fallbacks for missing data (senderName, text, chat title)
  • Display timestamps for context
  • Offer appropriate actions (open and copy)

Note: Address the sortKey handling flagged in the previous comment.

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 (7)
src/components/ChatListItem.tsx (2)

6-17: Consider removing unused avatarUrl property from the interface.

The avatarUrl property (line 14) is defined in the Chat interface but is never used within this component. The getChatIcon function handles avatar logic internally by accessing chat.participants. If avatarUrl is intended for future use or is passed through for other purposes, consider documenting this; otherwise, removing it would reduce interface clutter.


28-29: Remove redundant key prop from List.Item.

The key={chat.id} on line 29 is unnecessary here. React keys are needed on the outermost element returned from map() calls, which is the parent's responsibility (and correctly done in search-chats.tsx at line 60). The key prop inside this component has no effect on reconciliation.

     <List.Item
-      key={chat.id}
       icon={getChatIcon(chat)}
src/search-chats.tsx (1)

9-22: getAvatarUrl result appears unused.

The getAvatarUrl function extracts an avatar URL, but the returned value assigned to avatarUrl (line 63) is never consumed by ChatListItem. The getChatIcon function inside ChatListItem independently derives the avatar from chat.participants. Consider removing this helper and the avatarUrl assignment to eliminate dead code.

-/**
- * Returns raw avatar URL for 1:1 chats, undefined for groups.
- * Note: The URL is not sanitized here - ChatListItem handles sanitization via safeAvatarPath.
- */
-// eslint-disable-next-line @typescript-eslint/no-explicit-any
-function getAvatarUrl(chat: any): string | undefined {
-  // Only show avatar for 1:1 chats, not groups
-  if (chat.type !== "group" && chat.participants?.items && Array.isArray(chat.participants.items)) {
-    // eslint-disable-next-line @typescript-eslint/no-explicit-any
-    const otherParticipant = chat.participants.items.find((p: any) => !p.isSelf);
-    return otherParticipant?.imgURL;
-  }
-  return undefined;
-}

And in the chat object spread:

             <ChatListItem
               key={chat.id}
               chat={{
                 ...chat,
-                avatarUrl: getAvatarUrl(chat),
                 onOpen: () => focusApp({ chatID: chat.id }),
               }}
src/unread-chats.tsx (3)

68-94: Consider using ChatListItem for consistency.

This file renders List.Item inline with similar logic to what ChatListItem already encapsulates (icon via getChatIcon, title fallback to unnamed chat, network subtitle, actions). Using ChatListItem would reduce duplication and ensure consistent behavior across chat list views.

+import { ChatListItem } from "./components/ChatListItem";
+
 // In the render:
-        chats.map((chat) => (
-          <List.Item
-            key={chat.id}
-            icon={getChatIcon(chat)}
-            title={chat.title || translations.common.unnamedChat}
-            subtitle={chat.network}
-            accessories={[
-              {
-                text: translations.commands.unreadChats.unreadCount(chat.unreadCount),
-                icon: Icon.Bubble,
-              },
-              ...(chat.isPinned ? [{ icon: Icon.Pin }] : []),
-              ...(chat.isMuted ? [{ icon: Icon.SpeakerOff }] : []),
-              ...(chat.lastActivity ? [{ date: new Date(chat.lastActivity) }] : []),
-            ]}
-            actions={...}
-          />
-        ))
+        chats.map((chat) => (
+          <ChatListItem
+            key={chat.id}
+            chat={{
+              ...chat,
+              onOpen: () => focusApp({ chatID: chat.id }),
+            }}
+            translations={translations}
+            accessories={[
+              { text: translations.commands.unreadChats.unreadCount(chat.unreadCount), icon: Icon.Bubble },
+              ...(chat.isPinned ? [{ icon: Icon.Pin }] : []),
+              ...(chat.isMuted ? [{ icon: Icon.SpeakerOff }] : []),
+              ...(chat.lastActivity ? [{ date: new Date(chat.lastActivity) }] : []),
+            ]}
+          />
+        ))

Note: ChatListItem would need to support the lastActivity accessory pattern, or you could keep accessories as-is since they're passed through.


22-45: Consider adding a refresh mechanism.

The useBeeperDesktop hook fetches once on mount with no dependencies. Users expecting to see newly arrived unread messages would need to close and reopen the command. Consider exposing the revalidate function from usePromise (which useBeeperDesktop wraps) and adding a refresh action, or document this limitation.


1-1: Remove unused Image import.

Image is imported but not used in this file. The getChatIcon function is imported from ./utils/chatIcon and handles the Image.ImageLike type internally.

-import { ActionPanel, Action, List, Icon, Image } from "@raycast/api";
+import { ActionPanel, Action, List, Icon } from "@raycast/api";
src/list-chats.tsx (1)

18-18: Consider defining a typed interface for chats.

Using any[] loses type safety. If the BeeperDesktop API provides type definitions, consider using those, or define a local Chat interface with the properties you're using (id, title, network, type, lastActivity, accountID, unreadCount, isPinned, isMuted, isArchived, participants).

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 6670b89 and 954e284.

📒 Files selected for processing (7)
  • src/components/ChatListItem.tsx (1 hunks)
  • src/hooks/useChatSearch.ts (1 hunks)
  • src/list-chats.tsx (1 hunks)
  • src/locales/cs.ts (1 hunks)
  • src/locales/en.ts (1 hunks)
  • src/search-chats.tsx (1 hunks)
  • src/unread-chats.tsx (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/locales/en.ts
🧰 Additional context used
🧬 Code graph analysis (6)
src/locales/cs.ts (2)
src/locales/en.ts (1)
  • Translations (104-104)
src/locales/index.ts (1)
  • Translations (67-67)
src/search-chats.tsx (4)
src/locales/index.ts (1)
  • t (42-44)
src/hooks/useChatSearch.ts (1)
  • useChatSearch (3-36)
src/components/ChatListItem.tsx (1)
  • ChatListItem (26-45)
src/api.ts (1)
  • focusApp (86-96)
src/hooks/useChatSearch.ts (1)
src/api.ts (1)
  • useBeeperDesktop (79-84)
src/components/ChatListItem.tsx (3)
src/locales/en.ts (1)
  • Translations (104-104)
src/locales/index.ts (1)
  • Translations (67-67)
src/utils/chatIcon.ts (1)
  • getChatIcon (9-24)
src/list-chats.tsx (3)
src/locales/index.ts (1)
  • t (42-44)
src/api.ts (3)
  • useBeeperDesktop (79-84)
  • getBeeperDesktop (55-70)
  • focusApp (86-96)
src/utils/chatIcon.ts (1)
  • getChatIcon (9-24)
src/unread-chats.tsx (3)
src/locales/index.ts (1)
  • t (42-44)
src/api.ts (3)
  • useBeeperDesktop (79-84)
  • focusApp (86-96)
  • createBeeperOAuth (31-48)
src/utils/chatIcon.ts (1)
  • getChatIcon (9-24)
🔇 Additional comments (10)
src/components/ChatListItem.tsx (1)

26-45: LGTM!

The component is well-structured with proper conditional rendering for the details action, correct use of optional chaining for onOpen, and appropriate default values for optional props.

src/search-chats.tsx (2)

38-78: LGTM!

The search implementation correctly handles empty state, loading, and no-results scenarios. The throttle prop on the List is a good practice for search performance.


67-71: This review comment is based on incorrect code. The file src/search-chats.tsx does not contain any translations.common.unreadCount() calls or references to a translations object. The code snippet cited (lines 67-71) does not match the actual file content.

The actual file uses Raycast's built-in List.Item component and does reference chat.unreadCount in a template literal on line 36 (**Unread Count:** ${chat.unreadCount}), but this is simple string interpolation without a translations function call. No defensive handling is needed for this usage as string interpolation safely handles undefined values.

Likely an incorrect or invalid review comment.

src/hooks/useChatSearch.ts (1)

3-35: LGTM!

The pagination logic with safety limits (MAX_PAGES, early exit at 200 results) is well-designed. The dependency array correctly includes both searchText and includeEmpty, addressing the previous review feedback.

src/locales/cs.ts (1)

1-104: LGTM!

The Czech translation file correctly implements the Translations type, ensuring structural consistency with the English locale. The dynamic helper functions (unreadCount, totalCount) maintain the same signatures as their English counterparts.

Consider having a native Czech speaker review the translations for accuracy and natural phrasing, as noted in the PR description that AI assistance was used.

src/unread-chats.tsx (1)

16-98: LGTM on the overall structure!

The component correctly handles loading, error, and empty states with appropriate localized messages. The pagination with a safety limit and the server-side unreadOnly filter is a good approach. Sorting by unreadCount descending provides a sensible default ordering.

src/list-chats.tsx (4)

23-40: LGTM!

The search implementation correctly resets pagination state on query changes and uses the isLoading state to provide feedback during the transition. The throttle prop on the List component helps reduce excessive API calls.


76-112: LGTM!

Previous review feedback has been addressed:

  • Accessories array now uses spread operator to conditionally include the date (line 82)
  • Title fallback is applied in both the List.Item (line 80) and Detail markdown (line 95)

The action panel provides a complete set of useful actions for each chat item.


114-132: LGTM!

The conditional rendering logic correctly handles the various states:

  • Shows load-more only when there are existing results and more available
  • Displays loading feedback in the button title during pagination
  • Empty view provides appropriate guidance when no results exist

61-62: This review comment references code that does not exist in the current file.

The code snippet showing translations.commands.listChats.loadMoreError and translations.commands.listChats.loadMoreErrorMessage at lines 61-62 does not match the actual content of src/list-chats.tsx. Lines 61-62 contain onAction={() => focusApp()}, and there is no loadMore function, translations object, or toast error handling in this file. Additionally, no translation keys need to be verified as none are referenced in the current code.

Likely an incorrect or invalid review comment.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

1 participant