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__
This commit is contained in:
iliya 2026-03-22 17:36:11 +02:00
parent bbb34042a8
commit bf2220daf6
8 changed files with 28 additions and 13 deletions

View file

@ -88,6 +88,10 @@ export function useStableTeamMentionMeta(teams: readonly TeamSummary[]): TeamMen
null 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 ( if (
stableRef.current === null || stableRef.current === null ||
!areTeamMentionEntriesEqual(stableRef.current.entries, entries) !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; return stableRef.current.value;
} }

View file

@ -47,8 +47,11 @@ export function useViewportCommentRead({
const seenIdsRef = useRef<Set<string>>(new Set()); const seenIdsRef = useRef<Set<string>>(new Set());
const teamNameRef = useRef(teamName); const teamNameRef = useRef(teamName);
const taskIdRef = useRef(taskId); 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 // Reset tracked state when team/task changes
useEffect(() => { useEffect(() => {

View file

@ -51,7 +51,10 @@ export function useViewportObserver({
registerElement: (value: string) => (el: HTMLElement | null) => void; registerElement: (value: string) => (el: HTMLElement | null) => void;
} { } {
const onVisibleChangeRef = useRef(onVisibleChange); const onVisibleChangeRef = useRef(onVisibleChange);
onVisibleChangeRef.current = onVisibleChange;
useEffect(() => {
onVisibleChangeRef.current = onVisibleChange;
}, [onVisibleChange]);
const observerRef = useRef<IntersectionObserver | null>(null); const observerRef = useRef<IntersectionObserver | null>(null);
const visibleValuesRef = useRef<Set<string>>(new Set()); const visibleValuesRef = useRef<Set<string>>(new Set());
@ -67,6 +70,9 @@ export function useViewportObserver({
// and produce false positives for all visible elements. // and produce false positives for all visible elements.
if (!root) return; if (!root) return;
// Capture ref values for cleanup closure
const visibleValues = visibleValuesRef.current;
const observer = new IntersectionObserver( const observer = new IntersectionObserver(
(entries) => { (entries) => {
let changed = false; let changed = false;
@ -75,19 +81,19 @@ export function useViewportObserver({
if (!value) continue; if (!value) continue;
if (entry.isIntersecting) { if (entry.isIntersecting) {
if (!visibleValuesRef.current.has(value)) { if (!visibleValues.has(value)) {
visibleValuesRef.current.add(value); visibleValues.add(value);
changed = true; changed = true;
} }
} else { } else {
if (visibleValuesRef.current.has(value)) { if (visibleValues.has(value)) {
visibleValuesRef.current.delete(value); visibleValues.delete(value);
changed = true; changed = true;
} }
} }
} }
if (changed) { if (changed) {
onVisibleChangeRef.current(Array.from(visibleValuesRef.current)); onVisibleChangeRef.current(Array.from(visibleValues));
} }
}, },
{ root, threshold } { root, threshold }
@ -105,7 +111,7 @@ export function useViewportObserver({
return () => { return () => {
observer.disconnect(); observer.disconnect();
observerRef.current = null; observerRef.current = null;
visibleValuesRef.current.clear(); visibleValues.clear();
}; };
}, [root, threshold]); }, [root, threshold]);

View file

@ -48,7 +48,7 @@ export function initSentryRenderer(): void {
}; };
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- cross-version @sentry/core type mismatch // 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) { if (window.electronAPI) {
// Electron renderer — uses IPC transport to main process. // Electron renderer — uses IPC transport to main process.

View file

@ -39,7 +39,7 @@ function storageKey(teamName: string): string {
} }
/** Legacy keys used by the old three-key approach. */ /** Legacy keys used by the old three-key approach. */
function legacyKeys(teamName: string) { function legacyKeys(teamName: string): { text: string; chips: string; attachments: string } {
return { return {
text: `draft:compose:${teamName}`, text: `draft:compose:${teamName}`,
chips: `draft:compose:${teamName}:chips`, chips: `draft:compose:${teamName}:chips`,

View file

@ -695,7 +695,7 @@ export function initializeNotificationListeners(): () => void {
updateStatus: 'available', updateStatus: 'available',
availableVersion: s.version ?? null, availableVersion: s.version ?? null,
releaseNotes: s.releaseNotes ?? null, releaseNotes: s.releaseNotes ?? null,
showUpdateDialog: s.version !== dismissed, showUpdateDialog: (s.version ?? null) !== dismissed,
}); });
break; break;
} }

View file

@ -3,7 +3,7 @@ import {
MEMBER_COLOR_PALETTE, MEMBER_COLOR_PALETTE,
normalizeMemberColorName, normalizeMemberColorName,
} from '@shared/constants/memberColors'; } from '@shared/constants/memberColors';
import { isLeadAgentType, isLeadMember } from '@shared/utils/leadDetection'; import { isLeadMember } from '@shared/utils/leadDetection';
import type { import type {
LeadActivityState, LeadActivityState,

View file

@ -6,6 +6,7 @@
* (main: process.env, renderer: import.meta.env). * (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; declare const __APP_VERSION__: string;
/** Release identifier injected at build time via Vite `define`. */ /** Release identifier injected at build time via Vite `define`. */