From 17e12eaf0894b47a82d337a39504bf6e985c8e29 Mon Sep 17 00:00:00 2001 From: Afonso Jorge Ramos Date: Fri, 24 Jul 2026 02:25:27 +0100 Subject: [PATCH 1/2] refactor: retire fetch type notification setting TanStack Query owns notification fetching with standard interval polling, so the Interval vs Inactivity choice no longer earns its settings surface. Notifications now always poll on the configured fetch interval; the inactivity timer hook and the settings radio group are removed. A stale fetchType key in previously persisted settings is ignored harmlessly. --- src/renderer/__mocks__/state-mocks.ts | 2 - .../settings/NotificationSettings.test.tsx | 11 -- .../settings/NotificationSettings.tsx | 29 +---- .../hooks/timers/useInactivityTimer.ts | 67 ----------- src/renderer/hooks/useNotifications.test.tsx | 3 - src/renderer/hooks/useNotifications.ts | 22 +--- .../__snapshots__/Settings.test.tsx.snap | 107 +----------------- src/renderer/stores/defaults.ts | 2 - src/renderer/types.ts | 6 - 9 files changed, 12 insertions(+), 237 deletions(-) delete mode 100644 src/renderer/hooks/timers/useInactivityTimer.ts diff --git a/src/renderer/__mocks__/state-mocks.ts b/src/renderer/__mocks__/state-mocks.ts index 65acbd6ae..a9321221b 100644 --- a/src/renderer/__mocks__/state-mocks.ts +++ b/src/renderer/__mocks__/state-mocks.ts @@ -3,7 +3,6 @@ import { Constants } from '../constants'; import { type Account, type AppearanceSettingsState, - FetchType, GroupBy, type KeyboardAcceleratorShortcut, type NotificationSettingsState, @@ -34,7 +33,6 @@ const mockAppearanceSettings: AppearanceSettingsState = { const mockNotificationSettings: NotificationSettingsState = { groupBy: GroupBy.REPOSITORY, - fetchType: FetchType.INTERVAL, fetchInterval: Constants.DEFAULT_FETCH_NOTIFICATIONS_INTERVAL_MS, fetchAllNotifications: true, detailedNotifications: true, diff --git a/src/renderer/components/settings/NotificationSettings.test.tsx b/src/renderer/components/settings/NotificationSettings.test.tsx index cff5e8c57..9f63423db 100644 --- a/src/renderer/components/settings/NotificationSettings.test.tsx +++ b/src/renderer/components/settings/NotificationSettings.test.tsx @@ -31,17 +31,6 @@ describe('renderer/components/settings/NotificationSettings.tsx', () => { expect(updateSettingSpy).toHaveBeenCalledWith('groupBy', 'DATE'); }); - it('should change the fetchType radio group', async () => { - await act(async () => { - renderWithProviders(); - }); - - await userEvent.click(screen.getByTestId('radio-fetchType-inactivity')); - - expect(updateSettingSpy).toHaveBeenCalledTimes(1); - expect(updateSettingSpy).toHaveBeenCalledWith('fetchType', 'INACTIVITY'); - }); - describe('fetch interval settings', () => { it('should update the fetch interval values when using the buttons', async () => { await act(async () => { diff --git a/src/renderer/components/settings/NotificationSettings.tsx b/src/renderer/components/settings/NotificationSettings.tsx index 92aa5ccfd..7c3f0ebea 100644 --- a/src/renderer/components/settings/NotificationSettings.tsx +++ b/src/renderer/components/settings/NotificationSettings.tsx @@ -31,7 +31,7 @@ import { FieldLabel } from '../fields/FieldLabel'; import { RadioGroup } from '../fields/RadioGroup'; import { Title } from '../primitives/Title'; -import { FetchType, GroupBy, Size } from '../../types'; +import { GroupBy, Size } from '../../types'; import { hasAlternateScopes, hasRecommendedScopes } from '../../utils/auth/scopes'; import { openGitHubParticipatingDocs } from '../../utils/system/links'; @@ -48,7 +48,6 @@ export const NotificationSettings: FC = () => { // Setting store values const groupBy = useSettingsStore((s) => s.groupBy); - const fetchType = useSettingsStore((s) => s.fetchType); const fetchInterval = useSettingsStore((s) => s.fetchInterval); const fetchAllNotifications = useSettingsStore((s) => s.fetchAllNotifications); const detailedNotifications = useSettingsStore((s) => s.detailedNotifications); @@ -90,32 +89,6 @@ export const NotificationSettings: FC = () => { value={groupBy} /> - { - updateSetting('fetchType', evt.target.value as FetchType); - }} - options={[ - { label: 'Interval', value: FetchType.INTERVAL }, - { label: 'Inactivity', value: FetchType.INACTIVITY }, - ]} - tooltip={ - - Controls how new notifications are fetched. - - Interval will check for new notifications on a regular - scheduled interval. - - - Inactivity will check for new notifications only when there - has been no user activity within {APPLICATION.NAME} for a specified period of time. - - - } - value={fetchType} - /> - diff --git a/src/renderer/hooks/timers/useInactivityTimer.ts b/src/renderer/hooks/timers/useInactivityTimer.ts deleted file mode 100644 index cc70a26ec..000000000 --- a/src/renderer/hooks/timers/useInactivityTimer.ts +++ /dev/null @@ -1,67 +0,0 @@ -import { useCallback, useEffect, useRef } from 'react'; - -import { isOnline } from '../../utils/system/network'; - -const events = ['mousedown', 'keypress', 'click']; - -/** - * Hook that triggers a callback after a specified period of user inactivity. - * User activity (mousedown, keypress, click) resets the timer. - * - * @param callback - The function to call once inactivity exceeds `delay`. - * @param delay - Inactivity timeout in milliseconds. - */ -export const useInactivityTimer = (callback: () => void, delay: number | null) => { - const savedCallback = useRef<(() => void) | null>(null); - const timeoutRef = useRef | null>(null); - - // Remember the latest callback - useEffect(() => { - savedCallback.current = callback; - }, [callback]); - - // Reset the inactivity timer - const resetTimer = useCallback(() => { - if (timeoutRef.current) { - clearTimeout(timeoutRef.current); - } - - if (delay !== null && savedCallback.current) { - timeoutRef.current = setTimeout(() => { - // Fire callback once inactivity threshold reached if online - if (savedCallback.current && isOnline()) { - savedCallback.current(); - } - - // Schedule next run while still inactive - resetTimer(); - }, delay); - } - }, [delay]); - - // Set up event listeners for user activity - useEffect(() => { - if (delay === null) { - return; - } - - // Add event listeners to track activity - for (const event of events) { - document.addEventListener(event, resetTimer, { passive: true }); - } - - // Start initial timer - resetTimer(); - - // Cleanup function - return () => { - if (timeoutRef.current) { - clearTimeout(timeoutRef.current); - } - - for (const event of events) { - document.removeEventListener(event, resetTimer); - } - }; - }, [delay, resetTimer]); -}; diff --git a/src/renderer/hooks/useNotifications.test.tsx b/src/renderer/hooks/useNotifications.test.tsx index a8baf72f6..022e1272f 100644 --- a/src/renderer/hooks/useNotifications.test.tsx +++ b/src/renderer/hooks/useNotifications.test.tsx @@ -16,7 +16,6 @@ import { import { useAccountsStore, useFiltersStore, useSettingsStore } from '../stores'; import type { AccountNotifications, Percentage } from '../types'; -import { FetchType } from '../types'; import { Errors } from '../utils/core/errors'; import * as logger from '../utils/core/logger'; @@ -182,7 +181,6 @@ describe('renderer/hooks/useNotifications.ts', () => { getAllNotificationsMock.mockResolvedValue(mockSingleAccountNotifications); useSettingsStore.setState({ - fetchType: FetchType.INTERVAL, fetchInterval: 1000, }); @@ -222,7 +220,6 @@ describe('renderer/hooks/useNotifications.ts', () => { getAllNotificationsMock.mockResolvedValue(mockSingleAccountNotifications); useSettingsStore.setState({ - fetchType: FetchType.INTERVAL, fetchInterval: 1000, }); diff --git a/src/renderer/hooks/useNotifications.ts b/src/renderer/hooks/useNotifications.ts index 83aa849f9..72ec490f4 100644 --- a/src/renderer/hooks/useNotifications.ts +++ b/src/renderer/hooks/useNotifications.ts @@ -7,7 +7,6 @@ import { useAccountsStore, useFiltersStore, useSettingsStore } from '../stores'; import { type Account, type AccountNotifications, - FetchType, type GitifyError, type GitifyNotification, type Status, @@ -32,7 +31,6 @@ import { removeNotificationsForAccount } from '../utils/notifications/remove'; import { getNewNotifications } from '../utils/notifications/utils'; import { raiseSoundNotification } from '../utils/system/audio'; import { raiseNativeNotification } from '../utils/system/native'; -import { useInactivityTimer } from './timers/useInactivityTimer'; interface NotificationsState { status: Status; @@ -54,10 +52,10 @@ interface NotificationsState { interface UseNotificationsOptions { /** - * Run the singleton side effects (sound/native alerts for new notifications, - * inactivity-based refetching, and interval/focus/reconnect polling). Only - * the app-level effects host should enable this; all other consumers share - * the query cache without them. + * Run the singleton side effects (sound/native alerts for new notifications + * and interval/focus/reconnect polling). Only the app-level effects host + * should enable this; all other consumers share the query cache without + * them. * * Polling is owned by the singleton host because TanStack Query schedules * `refetchInterval` per mounted observer, not per query. Every consumer @@ -98,7 +96,6 @@ export const useNotifications = ({ // Setting store values const fetchReadNotifications = useSettingsStore((s) => s.fetchReadNotifications); const fetchParticipatingNotifications = useSettingsStore((s) => s.participating); - const fetchType = useSettingsStore((s) => s.fetchType); const fetchIntervalMs = useSettingsStore((s) => s.fetchInterval); const markAsDoneOnUnsubscribe = useSettingsStore((s) => s.markAsDoneOnUnsubscribe); const playSoundNewNotifications = useSettingsStore((s) => s.playSound); @@ -162,21 +159,12 @@ export const useNotifications = ({ // Only the singleton side-effects host polls. Other consumers share the // cached data and would otherwise each schedule their own refetch timer. - refetchInterval: withSideEffects && fetchType === FetchType.INTERVAL ? fetchIntervalMs : false, + refetchInterval: withSideEffects ? fetchIntervalMs : false, refetchOnMount: withSideEffects, refetchOnReconnect: withSideEffects, refetchOnWindowFocus: withSideEffects, }); - // Inactivity-based fetching re-fetches once the user has been idle for the - // configured interval, instead of polling on a fixed schedule. - useInactivityTimer( - () => { - refetch(); - }, - withSideEffects && fetchType === FetchType.INACTIVITY ? fetchIntervalMs : null, - ); - const notificationCount = getNotificationCount(notifications); const unreadNotificationCount = getUnreadNotificationCount(notifications); diff --git a/src/renderer/routes/__snapshots__/Settings.test.tsx.snap b/src/renderer/routes/__snapshots__/Settings.test.tsx.snap index dcb7a8dc1..d1bbc7900 100644 --- a/src/renderer/routes/__snapshots__/Settings.test.tsx.snap +++ b/src/renderer/routes/__snapshots__/Settings.test.tsx.snap @@ -717,101 +717,6 @@ exports[`renderer/routes/Settings.tsx > should render itself & its children 1`] -
- -
- - -
-
- - -
- -
should render itself & its children 1`] data-wrap="nowrap" >