Files
cinny/src/app/hooks/useTauriNotificationBadge.ts
T
jared 668bdaad7d fix(wave-2): audit fixes — account-data races, search-cache wipe, export, media
Web fixes from the Wave-2 bug-hunt (findings in LOTUS_TODO):
- F1 (security): wipe the decrypted-plaintext search index on SERVER-FORCED
  logout too (token expiry / remote sign-out) — only manual logout did before.
  F4: the delete no longer reports success while onblocked (waits, 3s cap).
- M1/M2 (data-loss): useBookmarks + useUserNotes account-data writes are now
  serialized at MODULE scope (single queue + latestRef per client, echo-driven),
  fixing the cross-instance lost-update clobber (useBookmarks mounts per message
  row, so a per-instance queue was insufficient — caught in review).
- M6: room-history export gets a 200-page cap + Cancel + unmount-abort +
  correct date-range early-break (raw paginated ts). M4: image compression
  skips PNG (was flattening transparency to black), bakes EXIF orientation via
  createImageBitmap, .jpg-renames, and falls back to the original on decode
  failure instead of dropping the file. M5: MediaGallery lightbox opens the
  right item (shared thumb guard). M8: audio speed survives async decrypt.
- Desktop web wiring: D2 badge sums leaf rooms only (space double-count, like
  the favicon fix); D3 useTauriDnd re-hydrates from get_tray_dnd on mount; D5
  updater has a terminal state.

Reviewed; M7 reverted (past-time clamp is an intentional, tested contract).
tsc/eslint/prettier clean, build OK, 678 tests.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-02 20:56:27 -04:00

40 lines
1.7 KiB
TypeScript

import { useEffect, useRef } from 'react';
import { useAtomValue } from 'jotai';
import { roomToUnreadAtom } from '../state/room/roomToUnread';
// Tauri v2 injects __TAURI_INTERNALS__ into the webview at runtime.
// We use it directly so cinny doesn't need @tauri-apps/api as a dependency.
type TauriInternals = { invoke: (cmd: string, args?: Record<string, unknown>) => Promise<unknown> };
const tauriInvoke = (): TauriInternals['invoke'] | undefined =>
(window as unknown as { __TAURI_INTERNALS__?: TauriInternals }).__TAURI_INTERNALS__?.invoke;
export function useTauriNotificationBadge() {
const roomToUnread = useAtomValue(roomToUnreadAtom);
const prevHighlightsRef = useRef(0);
useEffect(() => {
const invoke = tauriInvoke();
if (!invoke) return;
let totalHighlights = 0;
roomToUnread.forEach((unread) => {
// Sum only leaf rooms (from === null); roomToUnread also holds per-ancestor
// space aggregates (from = Set), so counting all entries double-counts a
// space-nested room. Mirrors the favicon fix in ClientNonUIFeatures.
if (unread.from !== null) return;
totalHighlights += unread.highlight;
});
invoke('set_badge_count', { count: totalHighlights }).catch(() => {});
// Mirror the unread state onto the tray icon (visible when minimized to tray).
invoke('set_tray_unread', { unread: totalHighlights > 0 }).catch(() => {});
// Flash the taskbar button when new mentions arrive and the window is not
// focused, then reset the baseline.
if (totalHighlights > prevHighlightsRef.current && !document.hasFocus()) {
invoke('flash_window').catch(() => {});
}
prevHighlightsRef.current = totalHighlights;
}, [roomToUnread]);
}