From 5bfc638cd94b1986c688bbc93b6748d4958f17ca Mon Sep 17 00:00:00 2001 From: Harry19081 <20519290+Harry19081@users.noreply.github.com> Date: Sun, 23 Aug 2026 04:32:54 +0800 Subject: [PATCH 1/2] refactor(react): remove low-risk lint suppressions --- .../composerInput.pillPortals.tsx | 6 +---- src/contexts/workspace/ChatContext.tsx | 4 +--- .../ChatHistory/hooks/useChatScrollPin.ts | 9 +++++-- .../blocks/CanvasInlineCard/A2UIRenderer.tsx | 1 - .../blocks/CodeBlock/useCodeBlockState.ts | 1 - .../blocks/primitives/BlockOutput.tsx | 1 - .../__tests__/waitForSnapshotChange.test.ts | 2 -- src/features/GanttChart/index.tsx | 1 - .../Org2Cloud/useOrg2CloudRealtime.ts | 1 - .../AgentOrgs/hooks/useEnsureAgentDefs.ts | 11 ++++++--- .../playground/panels/PlaygroundChatPanel.tsx | 24 +++++++++---------- .../useIntegrationsCategoryTableProps.tsx | 1 - .../Integrations/useIntegrationsPage.tsx | 2 -- .../WorkManagement/GitHubWorkItemsSurface.tsx | 1 - .../BrowserLayout/useBrowserStatusBar.ts | 2 +- .../content/IssuesContent/index.tsx | 1 - .../TabBar/hooks/useTabLabelCollapse.ts | 3 +-- src/scaffold/ContextMenu/useContextMenu.ts | 2 +- src/store/repo/storage.ts | 2 +- src/util/secretScan/index.ts | 1 - 20 files changed, 33 insertions(+), 43 deletions(-) diff --git a/src/components/ComposerInput/composerInput.pillPortals.tsx b/src/components/ComposerInput/composerInput.pillPortals.tsx index 1fd00ca0d1..730f820433 100644 --- a/src/components/ComposerInput/composerInput.pillPortals.tsx +++ b/src/components/ComposerInput/composerInput.pillPortals.tsx @@ -118,11 +118,7 @@ export function useComposerPillPortals({ placeCaretAfterPill(insertedPill); insertedPill.removeAttribute("data-last-inserted-pill"); pendingCaretAfterPillRef.current = false; - // pendingCaretAfterPillRef is a stable ref passed in from the parent — - // listing it would not change behavior, only silence the linter's - // inability to see it originates from a `useRef()` in this scope. - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [hostRef, pillEntries]); + }, [hostRef, pendingCaretAfterPillRef, pillEntries]); return pillPortals; } diff --git a/src/contexts/workspace/ChatContext.tsx b/src/contexts/workspace/ChatContext.tsx index 531afd1c42..aa38b32e4b 100644 --- a/src/contexts/workspace/ChatContext.tsx +++ b/src/contexts/workspace/ChatContext.tsx @@ -115,9 +115,7 @@ export const ChatProvider: React.FC<{ children: ReactNode }> = ({ setFeedBackInfo, chatContainerRef, // ref is stable }), - // These are all stable references from useState/useCallback/useRef - // eslint-disable-next-line react-hooks/exhaustive-deps - [] + [setChatWidth] ); const historyActionsValue = useMemo( diff --git a/src/engines/ChatPanel/ChatHistory/hooks/useChatScrollPin.ts b/src/engines/ChatPanel/ChatHistory/hooks/useChatScrollPin.ts index 3cea110f23..8b40666257 100644 --- a/src/engines/ChatPanel/ChatHistory/hooks/useChatScrollPin.ts +++ b/src/engines/ChatPanel/ChatHistory/hooks/useChatScrollPin.ts @@ -102,6 +102,7 @@ export function useChatScrollPin({ }, [bottomInset, footerSpacerHeight, staticScrollerRef, virtuosoScrollerRef]); const scheduleFollowToEnd = useCallback(() => { + // eslint-disable-next-line react-hooks/immutability -- These caller-owned refs are the documented mutable coordination channel between the scroll hooks. effectiveManualScrollAtRef.current = 0; programmaticScrollAtRef.current = performance.now(); let secondFrameId = 0; @@ -190,8 +191,12 @@ export function useChatScrollPin({ return () => { el.removeEventListener("scroll", handleScroll); }; - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [virtuosoScrollerRef, pinLastGroupRef]); + }, [ + virtuosoScrollerRef, + pinLastGroupRef, + programmaticScrollAtRef, + effectiveManualScrollAtRef, + ]); return { scrollToEnd, programmaticScrollAtRef }; } diff --git a/src/engines/ChatPanel/blocks/CanvasInlineCard/A2UIRenderer.tsx b/src/engines/ChatPanel/blocks/CanvasInlineCard/A2UIRenderer.tsx index 40d2366d0f..f0dd7b6415 100644 --- a/src/engines/ChatPanel/blocks/CanvasInlineCard/A2UIRenderer.tsx +++ b/src/engines/ChatPanel/blocks/CanvasInlineCard/A2UIRenderer.tsx @@ -260,7 +260,6 @@ const A2UIRenderer = forwardRef( evalScript(js: string) { try { // Sandboxed eval: no iframe boundary, but wrapped in try/catch. - // eslint-disable-next-line no-new-func new Function(js)(); } catch (err) { log.error("[canvas_eval]", err); diff --git a/src/engines/ChatPanel/blocks/CodeBlock/useCodeBlockState.ts b/src/engines/ChatPanel/blocks/CodeBlock/useCodeBlockState.ts index 559722ef1f..0c70c8078d 100644 --- a/src/engines/ChatPanel/blocks/CodeBlock/useCodeBlockState.ts +++ b/src/engines/ChatPanel/blocks/CodeBlock/useCodeBlockState.ts @@ -36,7 +36,6 @@ export interface UseCodeBlockStateOptions { isCollapsed: boolean; } -// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types export function useCodeBlockState({ code, language, diff --git a/src/engines/ChatPanel/blocks/primitives/BlockOutput.tsx b/src/engines/ChatPanel/blocks/primitives/BlockOutput.tsx index e50e7d570a..9d58fcf574 100644 --- a/src/engines/ChatPanel/blocks/primitives/BlockOutput.tsx +++ b/src/engines/ChatPanel/blocks/primitives/BlockOutput.tsx @@ -338,7 +338,6 @@ const BlockOutput: React.FC = memo( ) : highlightLang && highlightedHtml ? (
) : ( diff --git a/src/engines/SessionCore/utils/__tests__/waitForSnapshotChange.test.ts b/src/engines/SessionCore/utils/__tests__/waitForSnapshotChange.test.ts index 1afdf2256c..6835b6e2f2 100644 --- a/src/engines/SessionCore/utils/__tests__/waitForSnapshotChange.test.ts +++ b/src/engines/SessionCore/utils/__tests__/waitForSnapshotChange.test.ts @@ -320,7 +320,6 @@ describe("waitForSnapshotChange — stress: many timeouts must not leak", () => timeoutMs: 10, }); vi.advanceTimersByTime(10); - // eslint-disable-next-line no-await-in-loop await expect(pending).resolves.toBe("timeout"); } expect(store.listeners.size).toBe(0); @@ -334,7 +333,6 @@ describe("waitForSnapshotChange — stress: many timeouts must not leak", () => timeoutMs: 100, }); store.emit(buildSnapshot(i + 1), `s${i}`); - // eslint-disable-next-line no-await-in-loop await expect(pending).resolves.toBe("snapshot"); } expect(store.listeners.size).toBe(0); diff --git a/src/features/GanttChart/index.tsx b/src/features/GanttChart/index.tsx index b1bce24871..98868f4c13 100644 --- a/src/features/GanttChart/index.tsx +++ b/src/features/GanttChart/index.tsx @@ -296,7 +296,6 @@ const GanttChart: React.FC = ({ return `task:${tasks[index - markerRows.length]?.id ?? index}`; }, }); - // eslint-disable-next-line react-hooks/incompatible-library -- TanStack Virtual exposes imperative helpers that cannot be memoized safely. const periodVirtualizer = useVirtualizer({ horizontal: true, count: periods.length, diff --git a/src/features/Org2Cloud/useOrg2CloudRealtime.ts b/src/features/Org2Cloud/useOrg2CloudRealtime.ts index cf6cef4e3f..9bcb1cb99b 100644 --- a/src/features/Org2Cloud/useOrg2CloudRealtime.ts +++ b/src/features/Org2Cloud/useOrg2CloudRealtime.ts @@ -463,7 +463,6 @@ export function useOrg2CloudRealtime(): void { connectionTeardownAtRef.current = Date.now(); }; // authRef (not auth) on purpose — see the ref comment above. - // eslint-disable-next-line react-hooks/exhaustive-deps }, [userId, endpointUrl, activeRealtimeOrgId]); // --- Nudge the socket to re-resolve its token as soon as the atom diff --git a/src/modules/MainApp/AgentOrgs/hooks/useEnsureAgentDefs.ts b/src/modules/MainApp/AgentOrgs/hooks/useEnsureAgentDefs.ts index 7966ea3fa6..df2ae2cb68 100644 --- a/src/modules/MainApp/AgentOrgs/hooks/useEnsureAgentDefs.ts +++ b/src/modules/MainApp/AgentOrgs/hooks/useEnsureAgentDefs.ts @@ -71,9 +71,14 @@ export function useEnsureAgentDefs(enabled = true): boolean { return () => { cancelled = true; }; - // Only run when `loaded` transitions from false to true (first time) - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [enabled, loaded]); + }, [ + enabled, + loaded, + setAllDefs, + setBuiltInAgents, + setCustomAgents, + setLoaded, + ]); return loaded; } diff --git a/src/modules/MainApp/Integrations/DevTools/playground/panels/PlaygroundChatPanel.tsx b/src/modules/MainApp/Integrations/DevTools/playground/panels/PlaygroundChatPanel.tsx index 4d546c387e..2ad9365f2d 100644 --- a/src/modules/MainApp/Integrations/DevTools/playground/panels/PlaygroundChatPanel.tsx +++ b/src/modules/MainApp/Integrations/DevTools/playground/panels/PlaygroundChatPanel.tsx @@ -67,6 +67,18 @@ const MOCK_INTERVENTION: AgentOrgMemberIntervention = { clearedAt: null, }; +function isInteractivePending( + event: SessionEvent, + canonicalName: string +): boolean { + return ( + stripMcpPrefix(event.functionName ?? "") === canonicalName && + (event.displayStatus === "awaiting_user" || + event.displayStatus === "running" || + event.displayStatus === "pending") + ); +} + // ============================================ // Public component // ============================================ @@ -121,20 +133,10 @@ export function PlaygroundChatPanel({ return items; }, [events]); - const isInteractivePending = ( - evt: (typeof events)[number], - canonicalName: string - ) => - stripMcpPrefix(evt.functionName ?? "") === canonicalName && - (evt.displayStatus === "awaiting_user" || - evt.displayStatus === "running" || - evt.displayStatus === "pending"); - const pendingAskUser = useMemo( () => events.find((evt) => isInteractivePending(evt, "ask_user_questions")) ?? null, - // eslint-disable-next-line react-hooks/exhaustive-deps [events] ); @@ -142,7 +144,6 @@ export function PlaygroundChatPanel({ () => events.find((evt) => isInteractivePending(evt, "suggest_mode_switch")) ?? null, - // eslint-disable-next-line react-hooks/exhaustive-deps [events] ); const showModeSwitchPreview = @@ -157,7 +158,6 @@ export function PlaygroundChatPanel({ () => events.find((evt) => isInteractivePending(evt, "ask_user_permissions")) ?? null, - // eslint-disable-next-line react-hooks/exhaustive-deps [events] ); diff --git a/src/modules/MainApp/Integrations/useIntegrationsCategoryTableProps.tsx b/src/modules/MainApp/Integrations/useIntegrationsCategoryTableProps.tsx index ef531e15a2..3274abc09d 100644 --- a/src/modules/MainApp/Integrations/useIntegrationsCategoryTableProps.tsx +++ b/src/modules/MainApp/Integrations/useIntegrationsCategoryTableProps.tsx @@ -59,7 +59,6 @@ export function useIntegrationsCategoryTableProps( handleModelsTabChange, } = params; - // eslint-disable-next-line react-hooks/preserve-manual-memoization const tableProps = useMemo( () => ({ category, diff --git a/src/modules/MainApp/Integrations/useIntegrationsPage.tsx b/src/modules/MainApp/Integrations/useIntegrationsPage.tsx index 07ba6b4246..f14cca51fb 100644 --- a/src/modules/MainApp/Integrations/useIntegrationsPage.tsx +++ b/src/modules/MainApp/Integrations/useIntegrationsPage.tsx @@ -122,7 +122,6 @@ export function useIntegrationsPage() { [extensions, navigate] ); - // eslint-disable-next-line react-hooks/preserve-manual-memoization -- intentional: deps include hook objects whose methods are called const handleCategoryChange = useCallback( (cat: IntegrationCategory) => { if (cat !== category) { @@ -156,7 +155,6 @@ export function useIntegrationsPage() { [accountsHook] ); - // eslint-disable-next-line react-hooks/preserve-manual-memoization -- intentional: deps include hook objects whose methods are called const handleAddAction = useCallback( (action: AddAction) => { switch (action) { diff --git a/src/modules/MainApp/WorkManagement/GitHubWorkItemsSurface.tsx b/src/modules/MainApp/WorkManagement/GitHubWorkItemsSurface.tsx index 7d63836551..4fece5b8b3 100644 --- a/src/modules/MainApp/WorkManagement/GitHubWorkItemsSurface.tsx +++ b/src/modules/MainApp/WorkManagement/GitHubWorkItemsSurface.tsx @@ -168,7 +168,6 @@ const GitHubWorkItemsSurface: React.FC = ({ useEffect(() => { if (!loading && currentPage > totalLoadedPages) { - // eslint-disable-next-line react-hooks/set-state-in-effect -- Remote result shrinkage requires clamping the controlled page. setCurrentPage(totalLoadedPages); } }, [currentPage, loading, setCurrentPage, totalLoadedPages]); diff --git a/src/modules/WorkStation/Browser/BrowserLayout/useBrowserStatusBar.ts b/src/modules/WorkStation/Browser/BrowserLayout/useBrowserStatusBar.ts index 573900ab5d..d3b55b0a78 100644 --- a/src/modules/WorkStation/Browser/BrowserLayout/useBrowserStatusBar.ts +++ b/src/modules/WorkStation/Browser/BrowserLayout/useBrowserStatusBar.ts @@ -132,7 +132,6 @@ export function useBrowserStatusBar({ if (ref.current) return; setStatusBarCallbacks({}); }; - // eslint-disable-next-line react-hooks/exhaustive-deps }, [ isActive, primarySidebarCollapsed, @@ -148,5 +147,6 @@ export function useBrowserStatusBar({ setAddToAgent, toastSuccess, chatSentToastMessage, + isMountedRef, ]); } diff --git a/src/modules/WorkStation/CodeEditor/Panels/EditorPrimarySidebar/content/IssuesContent/index.tsx b/src/modules/WorkStation/CodeEditor/Panels/EditorPrimarySidebar/content/IssuesContent/index.tsx index cbec12a0f5..61a0512a0f 100644 --- a/src/modules/WorkStation/CodeEditor/Panels/EditorPrimarySidebar/content/IssuesContent/index.tsx +++ b/src/modules/WorkStation/CodeEditor/Panels/EditorPrimarySidebar/content/IssuesContent/index.tsx @@ -287,7 +287,6 @@ const IssuesContent: React.FC = memo( openStatus, ]); - // eslint-disable-next-line react-hooks/incompatible-library -- TanStack Virtual exposes imperative helpers that cannot be memoized safely. const issueListVirtualizer = useVirtualizer({ count: virtualRows.length, getScrollElement: () => listRef.current, diff --git a/src/modules/WorkStation/shared/TabBar/hooks/useTabLabelCollapse.ts b/src/modules/WorkStation/shared/TabBar/hooks/useTabLabelCollapse.ts index 885635e7ca..d010071042 100644 --- a/src/modules/WorkStation/shared/TabBar/hooks/useTabLabelCollapse.ts +++ b/src/modules/WorkStation/shared/TabBar/hooks/useTabLabelCollapse.ts @@ -68,8 +68,7 @@ export function useTabLabelCollapse({ rafId = null; } }; - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [enabled, tabsDependency, activeTabDependency]); + }, [enabled, tabsDependency, activeTabDependency, containerRef]); return enabled && overflowCollapsed; } diff --git a/src/scaffold/ContextMenu/useContextMenu.ts b/src/scaffold/ContextMenu/useContextMenu.ts index 7d1c2f8278..a0285ce6b5 100644 --- a/src/scaffold/ContextMenu/useContextMenu.ts +++ b/src/scaffold/ContextMenu/useContextMenu.ts @@ -316,7 +316,7 @@ export function useContextMenu( onSelectRef.current?.(type, value, displayName); onCloseRef.current?.(); }, - [secondLayer, performSearch] // eslint-disable-line react-hooks/exhaustive-deps + [secondLayer, performSearch] ); // Go back — from drilled project to project list, or from project list to main menu diff --git a/src/store/repo/storage.ts b/src/store/repo/storage.ts index 3c0a9bb975..18e092989c 100644 --- a/src/store/repo/storage.ts +++ b/src/store/repo/storage.ts @@ -126,7 +126,7 @@ export function isValidUUID(uuid: string | undefined | null): boolean { */ export function resetRepoStore(): void { // Dynamic import to avoid circular dependency - // eslint-disable-next-line @typescript-eslint/no-require-imports, @typescript-eslint/no-var-requires + // eslint-disable-next-line @typescript-eslint/no-var-requires -- Runtime require intentionally breaks the storage/atom import cycle. const atoms = require("./atoms"); try { diff --git a/src/util/secretScan/index.ts b/src/util/secretScan/index.ts index 3d83228137..28621e0c8d 100644 --- a/src/util/secretScan/index.ts +++ b/src/util/secretScan/index.ts @@ -198,7 +198,6 @@ export function validateCustomPattern(pattern: string): string | null { const trimmed = pattern.trim(); if (!trimmed) return null; try { - // eslint-disable-next-line no-new new RegExp(trimmed); return null; } catch (err) { From 39f58e7c1e3fcdd7b0668d0edb3c7d2385edc66e Mon Sep 17 00:00:00 2001 From: Harry19081 <20519290+Harry19081@users.noreply.github.com> Date: Sun, 23 Aug 2026 04:59:45 +0800 Subject: [PATCH 2/2] chore(react): remove overlapping lint cleanup --- src/features/Org2Cloud/useOrg2CloudRealtime.ts | 1 + src/scaffold/ContextMenu/useContextMenu.ts | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/features/Org2Cloud/useOrg2CloudRealtime.ts b/src/features/Org2Cloud/useOrg2CloudRealtime.ts index 9bcb1cb99b..cf6cef4e3f 100644 --- a/src/features/Org2Cloud/useOrg2CloudRealtime.ts +++ b/src/features/Org2Cloud/useOrg2CloudRealtime.ts @@ -463,6 +463,7 @@ export function useOrg2CloudRealtime(): void { connectionTeardownAtRef.current = Date.now(); }; // authRef (not auth) on purpose — see the ref comment above. + // eslint-disable-next-line react-hooks/exhaustive-deps }, [userId, endpointUrl, activeRealtimeOrgId]); // --- Nudge the socket to re-resolve its token as soon as the atom diff --git a/src/scaffold/ContextMenu/useContextMenu.ts b/src/scaffold/ContextMenu/useContextMenu.ts index a0285ce6b5..7d1c2f8278 100644 --- a/src/scaffold/ContextMenu/useContextMenu.ts +++ b/src/scaffold/ContextMenu/useContextMenu.ts @@ -316,7 +316,7 @@ export function useContextMenu( onSelectRef.current?.(type, value, displayName); onCloseRef.current?.(); }, - [secondLayer, performSearch] + [secondLayer, performSearch] // eslint-disable-line react-hooks/exhaustive-deps ); // Go back — from drilled project to project list, or from project list to main menu