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
);
// 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;
}

View file

@ -47,8 +47,11 @@ export function useViewportCommentRead({
const seenIdsRef = useRef<Set<string>>(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(() => {

View file

@ -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<IntersectionObserver | null>(null);
const visibleValuesRef = useRef<Set<string>>(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]);

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
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.

View file

@ -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`,

View file

@ -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;
}

View file

@ -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,

View file

@ -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`. */