From e97152326b07bce98f2c60073506bd7a2d562dbe Mon Sep 17 00:00:00 2001 From: Jinjing <6427696+AmethystLiang@users.noreply.github.com> Date: Fri, 7 Aug 2026 18:53:01 -0700 Subject: [PATCH] Extract GitHub task search commit debouncing into a hook Move debounce logic from TaskPage into useGitHubTaskSearchCommit to prevent excessive GitHub API calls on every keystroke. Uses a 750ms idle window before committing search values. Add tests for the new hook. --- src/renderer/src/components/TaskPage.tsx | 21 ++++++---- .../use-github-task-search-commit.test.ts | 41 +++++++++++++++++++ .../use-github-task-search-commit.ts | 27 ++++++++++++ 3 files changed, 80 insertions(+), 9 deletions(-) create mode 100644 src/renderer/src/components/use-github-task-search-commit.test.ts create mode 100644 src/renderer/src/components/use-github-task-search-commit.ts diff --git a/src/renderer/src/components/TaskPage.tsx b/src/renderer/src/components/TaskPage.tsx index 2c3917ab1f4..d5f6bf0cf1e 100644 --- a/src/renderer/src/components/TaskPage.tsx +++ b/src/renderer/src/components/TaskPage.tsx @@ -431,6 +431,7 @@ import { type LinearOrderBy, type LinearViewMode } from '@/components/task-page-localized-options' +import { useGitHubTaskSearchCommit } from '@/components/use-github-task-search-commit' function isGitLabMRFilter(value: GitLabTaskFilter | GitLabIssueFilter): value is GitLabTaskFilter { return value === 'opened' || value === 'merged' || value === 'closed' || value === 'all' @@ -6533,19 +6534,21 @@ export default function TaskPage(): React.JSX.Element { ] ) - useEffect(() => { - if (!taskResumeApplied) { - return - } - const timeout = window.setTimeout(() => { - const scoped = scopeGitHubTaskSearch(taskSearchInput, activeGithubTaskKind) + const commitTaskSearch = useCallback( + (value: string): void => { + const scoped = scopeGitHubTaskSearch(value, activeGithubTaskKind) if (scoped !== appliedTaskSearch) { setTasksFiltering(true) } setAppliedTaskSearch(scoped) - }, TASK_SEARCH_DEBOUNCE_MS) - return () => window.clearTimeout(timeout) - }, [activeGithubTaskKind, appliedTaskSearch, taskSearchInput, taskResumeApplied]) + }, + [activeGithubTaskKind, appliedTaskSearch] + ) + useGitHubTaskSearchCommit({ + enabled: taskResumeApplied, + onCommit: commitTaskSearch, + value: taskSearchInput + }) useEffect(() => { if (!taskResumeApplied) { diff --git a/src/renderer/src/components/use-github-task-search-commit.test.ts b/src/renderer/src/components/use-github-task-search-commit.test.ts new file mode 100644 index 00000000000..cb85d516a63 --- /dev/null +++ b/src/renderer/src/components/use-github-task-search-commit.test.ts @@ -0,0 +1,41 @@ +// @vitest-environment happy-dom + +import { act, renderHook } from '@testing-library/react' +import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' +import { + GITHUB_TASK_SEARCH_IDLE_MS, + useGitHubTaskSearchCommit +} from './use-github-task-search-commit' + +describe('useGitHubTaskSearchCommit', () => { + beforeEach(() => vi.useFakeTimers()) + afterEach(() => vi.useRealTimers()) + + it('commits only the final value after a full idle window', () => { + const onCommit = vi.fn() + const view = renderHook( + ({ value }) => useGitHubTaskSearchCommit({ enabled: true, onCommit, value }), + { initialProps: { value: 'r' } } + ) + + act(() => vi.advanceTimersByTime(400)) + view.rerender({ value: 'ra' }) + act(() => vi.advanceTimersByTime(400)) + view.rerender({ value: 'rate' }) + act(() => vi.advanceTimersByTime(GITHUB_TASK_SEARCH_IDLE_MS - 1)) + + expect(onCommit).not.toHaveBeenCalled() + + act(() => vi.advanceTimersByTime(1)) + expect(onCommit).toHaveBeenCalledOnce() + expect(onCommit).toHaveBeenCalledWith('rate') + }) + + it('does not schedule a commit while disabled', () => { + const onCommit = vi.fn() + renderHook(() => useGitHubTaskSearchCommit({ enabled: false, onCommit, value: 'rate' })) + + act(() => vi.runAllTimers()) + expect(onCommit).not.toHaveBeenCalled() + }) +}) diff --git a/src/renderer/src/components/use-github-task-search-commit.ts b/src/renderer/src/components/use-github-task-search-commit.ts new file mode 100644 index 00000000000..a756992e145 --- /dev/null +++ b/src/renderer/src/components/use-github-task-search-commit.ts @@ -0,0 +1,27 @@ +import { useEffect, useRef } from 'react' + +// Remote GitHub fan-out needs a longer idle window; Enter still commits immediately. +export const GITHUB_TASK_SEARCH_IDLE_MS = 750 + +type GitHubTaskSearchCommitOptions = { + enabled: boolean + onCommit: (value: string) => void + value: string +} + +export function useGitHubTaskSearchCommit({ + enabled, + onCommit, + value +}: GitHubTaskSearchCommitOptions): void { + const onCommitRef = useRef(onCommit) + onCommitRef.current = onCommit + + useEffect(() => { + if (!enabled) { + return + } + const timeout = window.setTimeout(() => onCommitRef.current(value), GITHUB_TASK_SEARCH_IDLE_MS) + return () => window.clearTimeout(timeout) + }, [enabled, value]) +}