From 3528ad45153ca717f301a5af893b5d716e60dd54 Mon Sep 17 00:00:00 2001 From: diegosouzapw Date: Thu, 28 May 2026 13:30:41 -0300 Subject: [PATCH] fix(agent-skills): add 200ms debounce to preview pane lazy-fetch (GAP-C) --- .../agent-skills/AgentSkillsPageClient.tsx | 12 ++++++++-- tests/unit/agent-skills-page.test.tsx | 22 +++++++++++++++---- 2 files changed, 28 insertions(+), 6 deletions(-) diff --git a/src/app/(dashboard)/dashboard/agent-skills/AgentSkillsPageClient.tsx b/src/app/(dashboard)/dashboard/agent-skills/AgentSkillsPageClient.tsx index 355595bd68..6521e0e3fe 100644 --- a/src/app/(dashboard)/dashboard/agent-skills/AgentSkillsPageClient.tsx +++ b/src/app/(dashboard)/dashboard/agent-skills/AgentSkillsPageClient.tsx @@ -111,12 +111,20 @@ export function AgentSkillsPageClient(): JSX.Element { [markdownCache], ); + // ── Debounced preview load (200ms) ─────────────────────────────────────── + useEffect(() => { + if (!selectedId) return; + const timer = setTimeout(() => { + void loadPreview(selectedId); + }, 200); + return () => clearTimeout(timer); + }, [selectedId, loadPreview]); + const handleSelectCard = useCallback( (id: string) => { setSelectedId(id); - void loadPreview(id); }, - [loadPreview], + [], ); const handleRefreshPreview = useCallback(() => { diff --git a/tests/unit/agent-skills-page.test.tsx b/tests/unit/agent-skills-page.test.tsx index df7acfe2ec..bae548fe9c 100644 --- a/tests/unit/agent-skills-page.test.tsx +++ b/tests/unit/agent-skills-page.test.tsx @@ -240,7 +240,8 @@ describe("AgentSkillsPageClient", () => { expect(cards.length).toBe(20); }); - it("clicking a card triggers preview fetch", async () => { + it("clicking a card triggers preview fetch after 200ms debounce", async () => { + vi.useFakeTimers(); const skills = make42Skills(); const fetchMock = mockFetch(skills, FULL_COVERAGE, "# omni-skill-0 doc"); vi.stubGlobal("fetch", fetchMock); @@ -261,11 +262,24 @@ describe("AgentSkillsPageClient", () => { firstCard?.click(); }); - // A raw fetch should have been made - const rawFetchCalls = (fetchMock as ReturnType).mock.calls.filter( + // Before debounce fires — raw fetch should NOT have been made yet + const rawFetchCallsBefore = (fetchMock as ReturnType).mock.calls.filter( ([url]: [string]) => typeof url === "string" && url.includes("/raw"), ); - expect(rawFetchCalls.length).toBeGreaterThan(0); + expect(rawFetchCallsBefore.length).toBe(0); + + // Advance timers past the 200ms debounce + await act(async () => { + vi.advanceTimersByTime(200); + }); + + // Now the raw fetch should have been triggered + const rawFetchCallsAfter = (fetchMock as ReturnType).mock.calls.filter( + ([url]: [string]) => typeof url === "string" && url.includes("/raw"), + ); + expect(rawFetchCallsAfter.length).toBeGreaterThan(0); + + vi.useRealTimers(); }); it("preview pane shows empty state when no card is selected", async () => {