feat(search): global conversation search in the topbar with deep-link nav

Topbar search box → debounced crm.search → dropdown of hits (chat/mail, highlighted
snippet). Clicking a hit switches to the right tab and focuses the exact thread:
mail → Inbox, chat → Messenger (via the SDK's new focusThreadId, 0.1.6). Snippet HTML
is escaped except the <em> highlight. Demo mode searches an in-memory set.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-23 14:07:26 +05:30
parent c874a726ef
commit 904d003d32
13 changed files with 1242 additions and 105 deletions
+6 -6
View File
@@ -37,7 +37,7 @@ function useRealtimeSocket(): MessageSocket | null {
const SHELL = isShellConfigured();
export function MessengerSdk() {
export function MessengerSdk({ focusThreadId }: { focusThreadId?: string | null } = {}) {
return (
<div className="view">
{!SHELL && (
@@ -55,23 +55,23 @@ export function MessengerSdk() {
Demo mode running on the SDK&apos;s mock adapter.
</div>
)}
<div className="miu-host">{SHELL ? <LiveHost /> : <DemoHost />}</div>
<div className="miu-host">{SHELL ? <LiveHost focusThreadId={focusThreadId} /> : <DemoHost focusThreadId={focusThreadId} />}</div>
</div>
);
}
// SHELL is a build-time constant, so exactly one of these mounts for the life of the app
// (Rules-of-Hooks safe — the other branch never renders).
function DemoHost() {
function DemoHost({ focusThreadId }: { focusThreadId?: string | null }) {
const adapter = useMemo<MessagingAdapter>(() => new MockAdapter(), []);
return (
<MessagingProvider adapter={adapter}>
<SdkMessenger />
<SdkMessenger focusThreadId={focusThreadId} />
</MessagingProvider>
);
}
function LiveHost() {
function LiveHost({ focusThreadId }: { focusThreadId?: string | null }) {
const { sdk } = useAppShell();
const { user } = useAuth();
const socket = useRealtimeSocket();
@@ -83,7 +83,7 @@ function LiveHost() {
if (!adapter) return <div className="miu-empty">Loading</div>;
return (
<MessagingProvider adapter={adapter}>
<SdkMessenger />
<SdkMessenger focusThreadId={focusThreadId} />
</MessagingProvider>
);
}