@@ -260,7 +269,7 @@ export function InboxPanel({
empty={
isSearching ? (
results !== null &&
- results.developers.length === 0 && (
+ developers.length === 0 && (
+ {isSearching && results === null && list !== null && (
+
+ )}
+
{!isSearching && (
)}
- {isSearching &&
- results !== null &&
- results.developers.length > 0 && (
-
-
- Start a conversation
-
-
- {results.developers.map(
- ({ user: developer, reason }) => (
- -
-
- onOpen({
- kind: 'direct',
- partner: developer,
- })
- }
- />
-
- ),
- )}
-
-
- )}
+ {developers.length > 0 && (
+
+
+ Start a conversation
+
+
+ {developers.map(({ user: developer, reason }) => (
+ -
+
+ onOpen({
+ kind: 'direct',
+ partner: developer,
+ })
+ }
+ />
+
+ ))}
+
+
+ )}
);
diff --git a/resources/js/lib/conversation-search.ts b/resources/js/lib/conversation-search.ts
new file mode 100644
index 000000000..0d775e244
--- /dev/null
+++ b/resources/js/lib/conversation-search.ts
@@ -0,0 +1,33 @@
+import type { ConversationSummary, User } from '@/types';
+
+/**
+ * Everything the server found for the term, then the conversations on hand that the inbox search would also find:
+ * by a partner's or member's name or handle, or an org's name. A conversation shows once, as its first copy.
+ */
+export function conversationsMatching(
+ term: string,
+ onHand: ConversationSummary[],
+ found: ConversationSummary[] = [],
+): ConversationSummary[] {
+ const needle = term.toLowerCase();
+ const isMatch = (value: string | null) =>
+ value?.toLowerCase().includes(needle) ?? false;
+ const isPersonMatch = ({ username, name }: User) =>
+ isMatch(username) || isMatch(name);
+
+ return [
+ ...found,
+ ...onHand.filter(
+ (conversation) =>
+ (conversation.partner !== null &&
+ isPersonMatch(conversation.partner)) ||
+ isMatch(conversation.name) ||
+ (conversation.members ?? []).some(({ user }) =>
+ isPersonMatch(user),
+ ),
+ ),
+ ].filter(
+ (conversation, index, all) =>
+ all.findIndex(({ id }) => id === conversation.id) === index,
+ );
+}
diff --git a/tests/Unit/ConversationSearch.test.ts b/tests/Unit/ConversationSearch.test.ts
new file mode 100644
index 000000000..569d5e76d
--- /dev/null
+++ b/tests/Unit/ConversationSearch.test.ts
@@ -0,0 +1,41 @@
+import assert from 'node:assert/strict';
+import { test } from 'node:test';
+import { conversationsMatching } from '../../resources/js/lib/conversation-search.ts';
+import type { ConversationSummary, User } from '../../resources/js/types';
+
+const person = (username: string, name: string) => ({ id: username, username, name }) as User;
+const ana = person('ana', 'Ana Lovelace');
+const ben = person('benji', 'Ben Hopper');
+
+const direct = (partner: User, changes: Partial