From bf2220daf6a8e9042b0d2150c8c3f462e22071f1 Mon Sep 17 00:00:00 2001 From: iliya Date: Sun, 22 Mar 2026 17:36:11 +0200 Subject: [PATCH] fix: resolve lint warnings in hooks, store, and sentry modules - Move ref assignments from render to useEffect (useViewportCommentRead, useViewportObserver) - Copy ref.current to local variable for effect cleanup closure - Add eslint-disable for intentional ref-as-cache pattern (useStableTeamMentionMeta) - Fix !== always-true comparison between undefined and null (store) - Add missing return types (sentry, composerDraftStorage) - Remove unused import isLeadAgentType (memberHelpers) - Suppress naming-convention warning for Vite-injected __APP_VERSION__ --- .../hooks/useStableTeamMentionMeta.ts | 5 +++++ src/renderer/hooks/useViewportCommentRead.ts | 7 +++++-- src/renderer/hooks/useViewportObserver.ts | 20 ++++++++++++------- src/renderer/sentry.ts | 2 +- src/renderer/services/composerDraftStorage.ts | 2 +- src/renderer/store/index.ts | 2 +- src/renderer/utils/memberHelpers.ts | 2 +- src/shared/utils/sentryConfig.ts | 1 + 8 files changed, 28 insertions(+), 13 deletions(-) diff --git a/src/renderer/hooks/useStableTeamMentionMeta.ts b/src/renderer/hooks/useStableTeamMentionMeta.ts index 3137441f..235f5e2f 100644 --- a/src/renderer/hooks/useStableTeamMentionMeta.ts +++ b/src/renderer/hooks/useStableTeamMentionMeta.ts @@ -88,6 +88,10 @@ export function useStableTeamMentionMeta(teams: readonly TeamSummary[]): TeamMen null ); + // Intentional ref-as-cache pattern: avoids allocating a new object every + // render while still returning a referentially-stable value when the + // underlying data hasn't changed. + if ( stableRef.current === null || !areTeamMentionEntriesEqual(stableRef.current.entries, entries) @@ -98,5 +102,6 @@ export function useStableTeamMentionMeta(teams: readonly TeamSummary[]): TeamMen }; } + // eslint-disable-next-line react-hooks/refs -- stable ref cache pattern return stableRef.current.value; } diff --git a/src/renderer/hooks/useViewportCommentRead.ts b/src/renderer/hooks/useViewportCommentRead.ts index c2bc464a..e9014205 100644 --- a/src/renderer/hooks/useViewportCommentRead.ts +++ b/src/renderer/hooks/useViewportCommentRead.ts @@ -47,8 +47,11 @@ export function useViewportCommentRead({ const seenIdsRef = useRef>(new Set()); const teamNameRef = useRef(teamName); const taskIdRef = useRef(taskId); - teamNameRef.current = teamName; - taskIdRef.current = taskId; + + useEffect(() => { + teamNameRef.current = teamName; + taskIdRef.current = taskId; + }, [teamName, taskId]); // Reset tracked state when team/task changes useEffect(() => { diff --git a/src/renderer/hooks/useViewportObserver.ts b/src/renderer/hooks/useViewportObserver.ts index cd21ec4d..1f3778e1 100644 --- a/src/renderer/hooks/useViewportObserver.ts +++ b/src/renderer/hooks/useViewportObserver.ts @@ -51,7 +51,10 @@ export function useViewportObserver({ registerElement: (value: string) => (el: HTMLElement | null) => void; } { const onVisibleChangeRef = useRef(onVisibleChange); - onVisibleChangeRef.current = onVisibleChange; + + useEffect(() => { + onVisibleChangeRef.current = onVisibleChange; + }, [onVisibleChange]); const observerRef = useRef(null); const visibleValuesRef = useRef>(new Set()); @@ -67,6 +70,9 @@ export function useViewportObserver({ // and produce false positives for all visible elements. if (!root) return; + // Capture ref values for cleanup closure + const visibleValues = visibleValuesRef.current; + const observer = new IntersectionObserver( (entries) => { let changed = false; @@ -75,19 +81,19 @@ export function useViewportObserver({ if (!value) continue; if (entry.isIntersecting) { - if (!visibleValuesRef.current.has(value)) { - visibleValuesRef.current.add(value); + if (!visibleValues.has(value)) { + visibleValues.add(value); changed = true; } } else { - if (visibleValuesRef.current.has(value)) { - visibleValuesRef.current.delete(value); + if (visibleValues.has(value)) { + visibleValues.delete(value); changed = true; } } } if (changed) { - onVisibleChangeRef.current(Array.from(visibleValuesRef.current)); + onVisibleChangeRef.current(Array.from(visibleValues)); } }, { root, threshold } @@ -105,7 +111,7 @@ export function useViewportObserver({ return () => { observer.disconnect(); observerRef.current = null; - visibleValuesRef.current.clear(); + visibleValues.clear(); }; }, [root, threshold]); diff --git a/src/renderer/sentry.ts b/src/renderer/sentry.ts index 06d81cfc..385d6bb6 100644 --- a/src/renderer/sentry.ts +++ b/src/renderer/sentry.ts @@ -48,7 +48,7 @@ export function initSentryRenderer(): void { }; // eslint-disable-next-line @typescript-eslint/no-explicit-any -- cross-version @sentry/core type mismatch - const beforeSend = (event: any) => (telemetryAllowed ? event : null); + const beforeSend = (event: any): any => (telemetryAllowed ? event : null); if (window.electronAPI) { // Electron renderer — uses IPC transport to main process. diff --git a/src/renderer/services/composerDraftStorage.ts b/src/renderer/services/composerDraftStorage.ts index 5a59e7cc..b8ec54d7 100644 --- a/src/renderer/services/composerDraftStorage.ts +++ b/src/renderer/services/composerDraftStorage.ts @@ -39,7 +39,7 @@ function storageKey(teamName: string): string { } /** Legacy keys used by the old three-key approach. */ -function legacyKeys(teamName: string) { +function legacyKeys(teamName: string): { text: string; chips: string; attachments: string } { return { text: `draft:compose:${teamName}`, chips: `draft:compose:${teamName}:chips`, diff --git a/src/renderer/store/index.ts b/src/renderer/store/index.ts index 509534b9..28c9506e 100644 --- a/src/renderer/store/index.ts +++ b/src/renderer/store/index.ts @@ -695,7 +695,7 @@ export function initializeNotificationListeners(): () => void { updateStatus: 'available', availableVersion: s.version ?? null, releaseNotes: s.releaseNotes ?? null, - showUpdateDialog: s.version !== dismissed, + showUpdateDialog: (s.version ?? null) !== dismissed, }); break; } diff --git a/src/renderer/utils/memberHelpers.ts b/src/renderer/utils/memberHelpers.ts index b44bf117..6be83942 100644 --- a/src/renderer/utils/memberHelpers.ts +++ b/src/renderer/utils/memberHelpers.ts @@ -3,7 +3,7 @@ import { MEMBER_COLOR_PALETTE, normalizeMemberColorName, } from '@shared/constants/memberColors'; -import { isLeadAgentType, isLeadMember } from '@shared/utils/leadDetection'; +import { isLeadMember } from '@shared/utils/leadDetection'; import type { LeadActivityState, diff --git a/src/shared/utils/sentryConfig.ts b/src/shared/utils/sentryConfig.ts index 186a8f25..c4cde748 100644 --- a/src/shared/utils/sentryConfig.ts +++ b/src/shared/utils/sentryConfig.ts @@ -6,6 +6,7 @@ * (main: process.env, renderer: import.meta.env). */ +// eslint-disable-next-line @typescript-eslint/naming-convention -- Vite `define` injects this global declare const __APP_VERSION__: string; /** Release identifier injected at build time via Vite `define`. */