From 2537348b2dcd90bc168f6391abadf7207d5dc8a5 Mon Sep 17 00:00:00 2001 From: Zero Liu Date: Sat, 23 May 2026 23:37:56 -0700 Subject: [PATCH] Fix lint error --- src/agentMode/backends/shared/BinaryPathSetting.tsx | 1 + src/agentMode/skills/ui/SkillsSettings.tsx | 2 ++ src/components/chat-components/ChatInput.tsx | 6 +++++- src/components/chat-components/ChatModeInput.tsx | 2 ++ src/components/chat-components/ChatSingleMessage.test.tsx | 1 + src/components/chat-components/CopilotSpinner.tsx | 4 ++-- .../chat-components/attachChatViewLayoutObservers.ts | 4 ++-- src/components/ui/ModelEffortPicker.tsx | 2 ++ .../ui/dialogs/ConfigureProviderDialog.test.tsx | 2 ++ src/modelManagement/ui/tabs/ByokPanel.test.tsx | 2 ++ 10 files changed, 21 insertions(+), 5 deletions(-) diff --git a/src/agentMode/backends/shared/BinaryPathSetting.tsx b/src/agentMode/backends/shared/BinaryPathSetting.tsx index a88708bb..dd18c573 100644 --- a/src/agentMode/backends/shared/BinaryPathSetting.tsx +++ b/src/agentMode/backends/shared/BinaryPathSetting.tsx @@ -45,6 +45,7 @@ export const BinaryPathSetting: React.FC = ({ const [busy, setBusy] = React.useState(false); React.useEffect(() => { + // eslint-disable-next-line @eslint-react/hooks-extra/no-direct-set-state-in-use-effect -- sync the editable draft when the persisted path changes underneath us (e.g. auto-detect from another panel); a key-prop remount would drop in-flight edits setPathInput(initialPath); }, [initialPath]); diff --git a/src/agentMode/skills/ui/SkillsSettings.tsx b/src/agentMode/skills/ui/SkillsSettings.tsx index 8ae1b130..0218cb1b 100644 --- a/src/agentMode/skills/ui/SkillsSettings.tsx +++ b/src/agentMode/skills/ui/SkillsSettings.tsx @@ -104,8 +104,10 @@ export const SkillsSettings: React.FC = () => { // (e.g. via Reset Settings). We don't want to clobber the user's in-flight // typing, so only sync when the persisted value changes. useEffect(() => { + /* eslint-disable @eslint-react/hooks-extra/no-direct-set-state-in-use-effect -- resync the local draft when persisted settings change underneath us (e.g. Reset Settings); see comment above */ setDraft(persistedFolder); setValidationError(null); + /* eslint-enable @eslint-react/hooks-extra/no-direct-set-state-in-use-effect */ }, [persistedFolder]); // Trigger a discovery pass on mount and on persisted-folder change so diff --git a/src/components/chat-components/ChatInput.tsx b/src/components/chat-components/ChatInput.tsx index 9ad66fb8..df14e3a7 100644 --- a/src/components/chat-components/ChatInput.tsx +++ b/src/components/chat-components/ChatInput.tsx @@ -491,7 +491,11 @@ const ChatInput = React.forwardRef(function Cha } break; case "images": { - const input = document.createElement("input"); + // Use the input's own document so the file picker opens in the window + // hosting this chat view (popout-safe), not whichever window is focused. + const doc = containerRef.current?.doc; + if (!doc) break; + const input = doc.createElement("input"); input.type = "file"; input.accept = "image/*"; input.multiple = true; diff --git a/src/components/chat-components/ChatModeInput.tsx b/src/components/chat-components/ChatModeInput.tsx index 1ac6bd71..43860b2f 100644 --- a/src/components/chat-components/ChatModeInput.tsx +++ b/src/components/chat-components/ChatModeInput.tsx @@ -34,11 +34,13 @@ const ChatModeInput: React.FC = (props) => { // Force off in Projects mode; otherwise mirror settings. useEffect(() => { + /* eslint-disable @eslint-react/hooks-extra/no-direct-set-state-in-use-effect -- mirror the persisted setting / chain into the local toggle; the toggle is also user-editable so it can't be pure derived state */ if (currentChain === ChainType.PROJECT_CHAIN) { setAutonomousAgentToggle(false); } else { setAutonomousAgentToggle(settings.enableAutonomousAgent); } + /* eslint-enable @eslint-react/hooks-extra/no-direct-set-state-in-use-effect */ }, [settings.enableAutonomousAgent, currentChain]); const chatInputRef = useRef(null); diff --git a/src/components/chat-components/ChatSingleMessage.test.tsx b/src/components/chat-components/ChatSingleMessage.test.tsx index 817d5c00..913b1bf8 100644 --- a/src/components/chat-components/ChatSingleMessage.test.tsx +++ b/src/components/chat-components/ChatSingleMessage.test.tsx @@ -53,6 +53,7 @@ jest.mock("obsidian", () => { TFile: class {}, App: class {}, ItemView: class { + // eslint-disable-next-line obsidianmd/prefer-active-doc -- jsdom mock creating the root containerEl; single realm, no owner element to derive `.doc` from containerEl = document.createElement("div"); }, WorkspaceLeaf: class {}, diff --git a/src/components/chat-components/CopilotSpinner.tsx b/src/components/chat-components/CopilotSpinner.tsx index 67fa2a59..c675e2b2 100644 --- a/src/components/chat-components/CopilotSpinner.tsx +++ b/src/components/chat-components/CopilotSpinner.tsx @@ -24,12 +24,12 @@ export const CopilotSpinner: React.FC = () => { viewBox={`0 0 ${GRID_SIZE} ${GRID_SIZE}`} className="copilot-spinner" > - {SIGMA_DOTS.map((dot, index) => { + {SIGMA_DOTS.map((dot) => { const cx = dot.col * (DOT_SIZE + DOT_GAP) + DOT_SIZE / 2; const cy = dot.row * (DOT_SIZE + DOT_GAP) + DOT_SIZE / 2; return ( 0); }; const keyboardObserver = new MutationObserver(syncKeyboardClass); - keyboardObserver.observe(document.documentElement, { + keyboardObserver.observe(containerEl.doc.documentElement, { attributes: true, attributeFilter: ["style"], }); diff --git a/src/components/ui/ModelEffortPicker.tsx b/src/components/ui/ModelEffortPicker.tsx index d006846b..1885598a 100644 --- a/src/components/ui/ModelEffortPicker.tsx +++ b/src/components/ui/ModelEffortPicker.tsx @@ -75,6 +75,7 @@ export function ModelEffortPicker({ override, className }: ModelEffortPickerProp // on open. useEffect(() => { if (open) { + /* eslint-disable @eslint-react/hooks-extra/no-direct-set-state-in-use-effect -- seed the editable draft from props when the popover opens; drafts are committed on close, so this can't be pure derived state */ const initial = value && enabledKeys.includes(value) ? value : (enabledKeys[0] ?? null); setHighlightKey(initial); setDraftModelKey(initial); @@ -87,6 +88,7 @@ export function ModelEffortPicker({ override, className }: ModelEffortPickerProp : null; setDraftEffort(initialEffort); initialRef.current = { model: value, effort: activeEffortValue }; + /* eslint-enable @eslint-react/hooks-extra/no-direct-set-state-in-use-effect */ } }, [open, value, enabledKeys, activeEffortValue, effortOptionsByModelKey]); diff --git a/src/modelManagement/ui/dialogs/ConfigureProviderDialog.test.tsx b/src/modelManagement/ui/dialogs/ConfigureProviderDialog.test.tsx index d2f8da1b..b631bf95 100644 --- a/src/modelManagement/ui/dialogs/ConfigureProviderDialog.test.tsx +++ b/src/modelManagement/ui/dialogs/ConfigureProviderDialog.test.tsx @@ -16,6 +16,7 @@ const mockRemoveRefs = jest.fn().mockResolvedValue(undefined); const mockGetProvider = jest.fn(); jest.mock("@/modelManagement/ui/ModelManagementContext", () => ({ + // eslint-disable-next-line @eslint-react/hooks-extra/no-unnecessary-use-prefix -- mocks the real `useModelManagement` hook; the name must match the export useModelManagement: () => ({ adapters: { verifyCredentials: mockVerifyCredentials }, setup: { byok: { addCatalogProvider: mockAddCatalogProvider } }, @@ -31,6 +32,7 @@ jest.mock("@/modelManagement/ui/ModelManagementContext", () => ({ catalogService: { getProvider: mockGetProvider }, }), })); +// eslint-disable-next-line @eslint-react/hooks-extra/no-unnecessary-use-prefix -- mocks the real `useApp` hook; the name must match the export jest.mock("@/context", () => ({ useApp: () => ({}) })); jest.mock("@/modelManagement/state/atoms", () => { const jotai = jest.requireActual("jotai"); diff --git a/src/modelManagement/ui/tabs/ByokPanel.test.tsx b/src/modelManagement/ui/tabs/ByokPanel.test.tsx index d975af96..fbc5b908 100644 --- a/src/modelManagement/ui/tabs/ByokPanel.test.tsx +++ b/src/modelManagement/ui/tabs/ByokPanel.test.tsx @@ -9,6 +9,7 @@ const mockAddProviderOpen = jest.fn(); const mockConfigureOpen = jest.fn(); jest.mock("@/modelManagement/ui/ModelManagementContext", () => ({ + // eslint-disable-next-line @eslint-react/hooks-extra/no-unnecessary-use-prefix -- mocks the real `useModelManagement` hook; the name must match the export useModelManagement: () => ({ catalogService: { ensureLoaded: mockEnsureLoaded, @@ -19,6 +20,7 @@ jest.mock("@/modelManagement/ui/ModelManagementContext", () => ({ coordinator: { removeProvider: mockRemoveProvider }, }), })); +// eslint-disable-next-line @eslint-react/hooks-extra/no-unnecessary-use-prefix -- mocks the real `useApp` hook; the name must match the export jest.mock("@/context", () => ({ useApp: () => ({}) })); jest.mock("@/modelManagement/state/atoms", () => { const jotai = jest.requireActual("jotai");