From 3b6de2fdacd7b73cd02421083d31487821db328a Mon Sep 17 00:00:00 2001 From: Lotus CI Date: Wed, 23 Sep 2026 19:32:42 -0400 Subject: [PATCH] fix(desktop): hydrate Focus Assist on mount; thread-aware toast quick reply MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - useTauriFocusAssist queries the new `get_focus_assist` command on mount. The native poll's first reading is emitted during app setup, before the page listens, and the atom resets on every reload — so with Focus Assist already on, notifications leaked until the OS state flipped (cinny-desktop #15). - The toast quick reply takes the real `threadId` from the notification data and replies inside the thread (cinny-desktop #17). Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01PPmy3tPq869XDW4njjVaKA --- src/app/hooks/useTauriFocusAssist.ts | 15 ++++++++++++++- src/app/hooks/useTauriToastActions.ts | 8 ++++++-- 2 files changed, 20 insertions(+), 3 deletions(-) diff --git a/src/app/hooks/useTauriFocusAssist.ts b/src/app/hooks/useTauriFocusAssist.ts index 15ade4ffb..d50165b92 100644 --- a/src/app/hooks/useTauriFocusAssist.ts +++ b/src/app/hooks/useTauriFocusAssist.ts @@ -1,6 +1,7 @@ +import { useEffect } from 'react'; import { useSetAtom } from 'jotai'; import { focusAssistActiveAtom } from '../state/focusAssist'; -import { useTauriEvent } from './useTauri'; +import { tauriInvoke, useTauriEvent } from './useTauri'; /** Detail shape of the `focus-assist-changed` event emitted by the native side. */ type FocusAssistChangedDetail = { @@ -21,4 +22,16 @@ export function useTauriFocusAssist(): void { useTauriEvent('focus-assist-changed', ({ active }) => setFocusAssist(active), ); + + // Hydrate on mount. The native poll only emits on a transition, and its first + // reading lands during app setup before this page is listening — so with Focus + // Assist already on at launch (or after a reload) notifications leaked until + // the OS state next flipped (cinny-desktop #15). `null` = no reading yet. + useEffect(() => { + tauriInvoke()?.('get_focus_assist') + .then((active) => { + if (typeof active === 'boolean') setFocusAssist(active); + }) + .catch(() => undefined); + }, [setFocusAssist]); } diff --git a/src/app/hooks/useTauriToastActions.ts b/src/app/hooks/useTauriToastActions.ts index 074b6062f..dc38b70d0 100644 --- a/src/app/hooks/useTauriToastActions.ts +++ b/src/app/hooks/useTauriToastActions.ts @@ -14,6 +14,8 @@ interface ActivateDetail { /** Payload of the `lotus-notification-reply` event (the inline reply box). */ interface ReplyDetail { roomId?: string; + /** Set for a thread notification: the reply goes into that thread. */ + threadId?: string; text?: string; } @@ -36,12 +38,14 @@ export function useTauriToastActions(): void { if (path) navigate(path); }); - useTauriEvent('lotus-notification-reply', ({ roomId, text }) => { + useTauriEvent('lotus-notification-reply', ({ roomId, threadId, text }) => { + // `roomId` is the real room id carried in the notification data, never the + // coalescing tag (`room:thread`, `lotus-invites`) — cinny-desktop #17. if (!roomId || !text) return; // #79 — a quick-reply failure (offline, no send permission, etc.) used to // be swallowed with no feedback, leaving the user believing it was sent. // Surface it via the in-app error toast instead. - mx.sendMessage(roomId, { msgtype: MsgType.Text, body: text }).catch(() => { + mx.sendMessage(roomId, threadId ?? null, { msgtype: MsgType.Text, body: text }).catch(() => { setToast( createErrorToast( 'Your quick reply could not be sent. Please try again.',