From 99daa53c8c1bb66a485998b529757e4399e34c6b Mon Sep 17 00:00:00 2001
From: Linus Rath <139418639+rathlinus@users.noreply.github.com>
Date: Tue, 25 Aug 2026 20:09:18 +0200
Subject: [PATCH] feat: search suggestions with recent searches and contact
autocomplete #845
---
components/mail/mail-app.tsx | 49 +++--
.../search/__tests__/search-box.test.tsx | 196 ++++++++++++++++++
components/search/search-box.tsx | 179 ++++++++++++++++
components/search/search-suggestions.tsx | 194 +++++++++++++++++
lib/__tests__/search-suggestions.test.ts | 103 +++++++++
lib/search-suggestions.ts | 94 +++++++++
locales/ar/common.json | 7 +-
locales/ca/common.json | 7 +-
locales/cs/common.json | 7 +-
locales/da/common.json | 7 +-
locales/de/common.json | 7 +-
locales/en/common.json | 7 +-
locales/es/common.json | 7 +-
locales/fa/common.json | 7 +-
locales/fr/common.json | 7 +-
locales/he/common.json | 7 +-
locales/hu/common.json | 7 +-
locales/it/common.json | 7 +-
locales/ja/common.json | 7 +-
locales/ko/common.json | 7 +-
locales/lv/common.json | 7 +-
locales/mn/common.json | 7 +-
locales/nl/common.json | 7 +-
locales/pl/common.json | 7 +-
locales/pt/common.json | 7 +-
locales/ro/common.json | 7 +-
locales/ru/common.json | 7 +-
locales/sk/common.json | 7 +-
locales/tr/common.json | 7 +-
locales/uk/common.json | 7 +-
locales/zh/common.json | 7 +-
stores/__tests__/search-history-store.test.ts | 48 +++++
stores/search-history-store.ts | 48 +++++
33 files changed, 1036 insertions(+), 50 deletions(-)
create mode 100644 components/search/__tests__/search-box.test.tsx
create mode 100644 components/search/search-box.tsx
create mode 100644 components/search/search-suggestions.tsx
create mode 100644 lib/__tests__/search-suggestions.test.ts
create mode 100644 lib/search-suggestions.ts
create mode 100644 stores/__tests__/search-history-store.test.ts
create mode 100644 stores/search-history-store.ts
diff --git a/components/mail/mail-app.tsx b/components/mail/mail-app.tsx
index da2069dc4..a9007f805 100644
--- a/components/mail/mail-app.tsx
+++ b/components/mail/mail-app.tsx
@@ -63,6 +63,9 @@ import { PromptDialog } from "@/components/ui/prompt-dialog";
import { TotpReauthDialog } from "@/components/totp-reauth-dialog";
import { DragDropProvider } from "@/contexts/drag-drop-context";
import { isFilterEmpty, activeFilterCount } from "@/lib/jmap/search-utils";
+import { SearchBox, type ContactSearchField } from "@/components/search/search-box";
+import type { ContactSuggestion } from "@/lib/search-suggestions";
+import { useSearchHistoryStore } from "@/stores/search-history-store";
import { WelcomeBanner } from "@/components/ui/welcome-banner";
import { NavigationRail } from "@/components/layout/navigation-rail";
import { SidebarAppsModal } from "@/components/layout/sidebar-apps-modal";
@@ -81,7 +84,7 @@ import { EML_IMPORT_ACCEPT, expandImportableEmails } from "@/lib/eml-import";
import { findDraftIdentityId, resolveComposeAccountEmail, resolveReplyFrom, type ReplyFromResolution } from "@/lib/reply-identity";
import { buildReplyRecipients, isSelfSent } from "@/lib/reply-recipients";
import { useProMultiAccountIdentities } from "@/hooks/use-pro-multi-account-identities";
-import { Search, Filter, ChevronDown, X, Paperclip, Star, Mail, MailOpen, RotateCcw, PenSquare, PenLine, CheckSquare, Square, AlertTriangle } from "lucide-react";
+import { Filter, ChevronDown, X, Paperclip, Star, Mail, MailOpen, RotateCcw, PenSquare, PenLine, CheckSquare, Square, AlertTriangle } from "lucide-react";
import { ResizeHandle } from "@/components/layout/resize-handle";
import { Button } from "@/components/ui/button";
import { useConfig } from "@/hooks/use-config";
@@ -2780,6 +2783,7 @@ export function MailApp({ linkSegments }: MailAppProps = {}) {
const handleSearch = async (query: string) => {
if (!client) return;
setSearchQuery(query);
+ useSearchHistoryStore.getState().addRecentSearch(query);
if (!isFilterEmpty(searchFilters)) {
await advancedSearch(client);
} else {
@@ -2787,6 +2791,16 @@ export function MailApp({ linkSegments }: MailAppProps = {}) {
}
};
+ // A contact picked from the search-bar suggestions (#845) becomes a
+ // from:/to: filter — the typed text was only ever the lookup key, so it is
+ // dropped rather than ANDed into the query as a full-text term.
+ const handleSelectContactSuggestion = async (contact: ContactSuggestion, field: ContactSearchField) => {
+ if (!client) return;
+ setSearchQuery("");
+ setSearchFilters({ [field]: contact.email });
+ await advancedSearch(client);
+ };
+
const handleClearSearch = async () => {
setSearchQuery("");
clearSearchFilters();
@@ -3513,30 +3527,15 @@ export function MailApp({ linkSegments }: MailAppProps = {}) {
)}
-
+