From d9fcffffe71f68fb9b736d3ba44d0011e5fcd779 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yal=C3=A7=C4=B1n=20Doksanbir?= Date: Fri, 11 Sep 2026 05:05:52 +0000 Subject: [PATCH] fix(web): stop auto-animate polling in the legacy sidebar The legacy sidebar attached @formkit/auto-animate to its project list and every thread list and never detached it. auto-animate polls the position of the container and each row on an interval for as long as it is attached, so an idle app with hundreds of threads kept a core busy doing nothing (#4693). Destroying the controller on detach cannot fix that: poll() installs its interval from an untracked timeout up to two seconds after attach, so a list destroyed inside that window starts polling afterwards with nothing left to stop it. A bulk removal also clones and animates every row (#3962). Drive the legacy lists with the default sidebar's list motion instead. A small hook creates it when the list attaches, runs one motion pass after a commit that changed the rendered rows, and disposes it as soon as the list detaches, so nothing runs while a list is idle. Rows still slide between positions and fade in, reduced motion is honoured, an emptied list resets its baseline rather than fading out rows a collapsed panel would clip, and the shared fade cap keeps large updates cheap. The default sidebar already moved off auto-animate in #9731, so the dependency goes with its last user. Fixes #4693 --- apps/web/package.json | 1 - apps/web/src/components/LegacySidebar.tsx | 55 ++++------- .../sidebar/useSidebarListMotion.test.tsx | 95 +++++++++++++++++++ .../sidebar/useSidebarListMotion.ts | 25 +++++ pnpm-lock.yaml | 12 +-- 5 files changed, 138 insertions(+), 50 deletions(-) create mode 100644 apps/web/src/components/sidebar/useSidebarListMotion.test.tsx create mode 100644 apps/web/src/components/sidebar/useSidebarListMotion.ts diff --git a/apps/web/package.json b/apps/web/package.json index c2ce73b42b11..dd505d6ee868 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -20,7 +20,6 @@ "@dnd-kit/sortable": "^10.0.0", "@dnd-kit/utilities": "^3.2.2", "@effect/atom-react": "catalog:", - "@formkit/auto-animate": "^0.9.0", "@legendapp/list": "catalog:", "@lexical/react": "^0.41.0", "@noble/hashes": "catalog:", diff --git a/apps/web/src/components/LegacySidebar.tsx b/apps/web/src/components/LegacySidebar.tsx index 81fd6047f13e..3ece8c69fade 100644 --- a/apps/web/src/components/LegacySidebar.tsx +++ b/apps/web/src/components/LegacySidebar.tsx @@ -25,7 +25,6 @@ import { import { EnvironmentMachineIcon } from "./EnvironmentMachineIcon"; import { ProjectFavicon } from "./ProjectFavicon"; import { useAtomValue } from "@effect/atom-react"; -import { autoAnimate } from "@formkit/auto-animate"; import React, { useCallback, useEffect, memo, useMemo, useRef, useState } from "react"; import { useShallow } from "zustand/react/shallow"; import { @@ -214,6 +213,7 @@ import { type SidebarProjectGroupMember, type SidebarProjectSnapshot, } from "../sidebarProjectGrouping"; +import { useSidebarListMotion } from "./sidebar/useSidebarListMotion"; const SIDEBAR_SORT_LABELS: Record = { updated_at: "Last user message", created_at: "Created at", @@ -223,10 +223,6 @@ const SIDEBAR_THREAD_SORT_LABELS: Record = { updated_at: "Last user message", created_at: "Created at", }; -const SIDEBAR_LIST_ANIMATION_OPTIONS = { - duration: 180, - easing: "ease-out", -} as const; const EMPTY_THREAD_JUMP_LABELS = new Map(); const PROJECT_GROUPING_MODE_LABELS: Record = { repository: "Group by repository", @@ -963,7 +959,6 @@ interface SidebarProjectThreadListProps { confirmingArchiveThreadKey: string | null; setConfirmingArchiveThreadKey: React.Dispatch>; confirmArchiveButtonRefs: React.RefObject>; - attachThreadListAutoAnimateRef: (node: HTMLElement | null) => void; handleThreadClick: ( event: React.MouseEvent, threadRef: ScopedThreadRef, @@ -1019,7 +1014,6 @@ const SidebarProjectThreadList = memo(function SidebarProjectThreadList( confirmingArchiveThreadKey, setConfirmingArchiveThreadKey, confirmArchiveButtonRefs, - attachThreadListAutoAnimateRef, handleThreadClick, navigateToThread, onFileDropThreads, @@ -1035,11 +1029,20 @@ const SidebarProjectThreadList = memo(function SidebarProjectThreadList( } = props; const showMoreButtonRender = useMemo(() =>